Architectural Design

Overview and Hierarchy

Software in rules_score is structured in three levels:

dependable_element   (SEooC — complete Safety Element out of Context)
└── component        (groups units; owns component-level integration tests and requirements)
    ├── unit         (smallest independently verifiable architectural element: implementation + unit tests)
    └── component    (components can be nested for deeper hierarchies)
        └── unit

Two rules apply:

  • unit targets must always be wrapped in a component — they cannot be placed directly under dependable_element.

  • component targets can be nested: a component may contain other components as well as units, allowing arbitrary depth.

This hierarchy exists for two complementary reasons:

  • Interrelations between units and components — A component defines a clear boundary within which its units collaborate. Grouping units into components keeps inter-unit coupling explicit and local, while the component’s public interface controls what the rest of the system can depend on. The static architecture diagrams document exactly which components expose which interfaces, preventing accidental cross-boundary dependencies.

  • Interface-driven safety analysis — The public_api diagrams at the SEooC level define the interfaces that external consumers may call. Failure modes in the FMEA reference individual interface items by name, establishing a direct traceability link from the safety analysis back to the architecture. Without a well-defined interface boundary this traceability would be impossible.

Each level of the hierarchy has a corresponding design artifact:

Level

Design rule

Content

SEooC / component

architectural_design

Static structure, dynamic behaviour, public API

Unit

unit_design

Implementation Details, unit-level sequences

A consistency check at build time verifies that every component and unit in the Bazel implementation tree also appears in the PlantUML target architecture diagrams. If anything is out of sync the build fails with a descriptive error.

Static Architecture

The static view describes the structural organisation of your software: what components and units exist, how they relate to each other, and which dependencies they carry. It is the primary input for the architecture consistency check.

PlantUML

Write a PlantUML class or component diagram that names every component and unit from your Bazel BUILD file.

@startuml MySeooc_StaticDesign

package "MySeooc" as MySeooc <<SEooC>> {
    component "KvsComponent" as KvsComponent <<component>> {
        component "KeyValueStore" as KeyValueStore <<unit>>
        component "StorageBackend" as StorageBackend <<unit>>
    }
}

@enduml

Valid PlantUML Definitions

The validator identifies elements by their stereotype, not by the PlantUML keyword used. Both package and component keywords are accepted at each level.

Stereotype

Valid PlantUML keywords

Meaning

Bazel rule

<<SEooC>>

package, component

Safety Element out of Context boundary

dependable_element

<<component>>

component, package

Architectural component

component

<<unit>>

component, package

Leaf implementation unit

unit

Ports and Interface Bindings

Elements with stereotype <<SEooC>> or <<component>> may declare ports and bind them to interfaces. This documents which external interfaces the element requires or provides.

@startuml MySeooc_StaticDesign

package "MySeooc" as MySeooc <<SEooC>> {
    component "KvsComponent" as KvsComponent <<component>> {
        component "KeyValueStore" as KeyValueStore <<unit>>
    }

    portin  " " as p_storage   ' required interface port
    portout " " as p_api       ' provided interface port
}

interface "score::storage" as storage
interface "kvsapi"         as kvsapi

p_storage -( storage : requires
p_api     )- kvsapi  : provides

@enduml

Rules:

  • portin / portout are declared inside the <<SEooC>> or <<component>> element.

  • -( binds a required (incoming) interface; )- binds a provided (outgoing) interface.

  • The --() lollipop syntax (e.g. port --() Interface) is treated as a plain association and does not carry interface-binding semantics.

  • Plain package without a stereotype cannot carry interface bindings.

Bazel

The PlantUML diagrams capture intended structure; the Bazel rules model the actual implementation. Using the same example as the diagram above — SEooC MySeooc containing component KvsComponent with units KeyValueStore and StorageBackend — the three rules work together like this:

architectural_design

declares which diagram files belong to which view category:

load("@score_tooling//bazel/rules/rules_score:rules_score.bzl", "architectural_design")

architectural_design(
    name   = "my_arch",
    static = ["static_design.puml"],  # the MySeooc_StaticDesign diagram above
    dynamic = ["sequence_design.puml"],
)

unit

one target per leaf unit (<<unit>> stereotype) in the diagram. The unit name must match the name used in the PlantUML. It ties together implementation targets, test targets, and an optional unit_design target (see Software Unit Design):

load("@score_tooling//bazel/rules/rules_score:rules_score.bzl", "unit")

# Unit for KeyValueStore
cc_library(name = "kvs_lib",       srcs = ["kvs.cpp"],      hdrs = ["kvs.h"])
cc_test   (name = "kvs_unit_test", srcs = ["kvs_test.cpp"], deps = [":kvs_lib"])

unit(
    name           = "KeyValueStore",
    unit_design    = [":kvs_unit_design"],
    implementation = [":kvs_lib"],
    tests          = [":kvs_unit_test"],
)

# Unit for StorageBackend
cc_library(name = "storage_lib",       srcs = ["storage_backend.cpp"], hdrs = ["storage_backend.h"])
cc_test   (name = "storage_unit_test", srcs = ["storage_test.cpp"],   deps = [":storage_lib"])

unit(
    name           = "StorageBackend",
    unit_design    = [":storage_unit_design"],
    implementation = [":storage_lib"],
    tests          = [":storage_unit_test"],
)

component

groups the units that belong to KvsComponent in the diagram. It aggregates one or more unit (or nested component) targets and links them to component-level requirements. Integration tests that verify the units working together are declared here:

load("@score_tooling//bazel/rules/rules_score:rules_score.bzl",
     "component", "component_requirements")

component_requirements(
    name = "kvs_comp_req",
    srcs = ["component_requirements.trlc"],
    deps = [":feature_req"],
)

# The component maps to KvsComponent in the PlantUML diagram
component(
    name         = "KvsComponent",
    requirements = [":kvs_comp_req"],
    components   = [":KeyValueStore", ":StorageBackend"],
    tests        = [],
)

Dynamic Architecture

The dynamic view describes behavioural aspects — sequences of interactions, state transitions, and activity flows. Dynamic diagrams document how your software behaves at runtime. They are not validated against the Bazel structure at build time.

PlantUML

@startuml MySeooc_WriteSequence

actor Caller
participant KeyValueStore
participant StorageBackend

Caller -> KeyValueStore : write(key, value)
KeyValueStore -> StorageBackend : flush()
StorageBackend --> KeyValueStore : OK
KeyValueStore --> Caller : Result::Ok

@enduml

Bazel

architectural_design(
    name    = "my_arch",
    static  = ["static_design.puml"],
    dynamic = ["sequence.puml"],
)

Public API

The public API view describes the interface your SEooC exposes to its environment. These diagrams are linked to safety analysis: FailureMode records reference interface items by name (via the interface field), enabling traceability from each failure mode back to the architecture.

PlantUML

@startuml MySeooc_PublicApi

interface "KeyValueStore" as KVS {
    + write(key: string, value: bytes): Result
    + read(key: string): Optional<bytes>
}

@enduml

Bazel

architectural_design(
    name       = "my_arch",
    public_api = ["public_api.puml"],
)

The public_api attribute also generates traceability items that can be referenced by fmea targets (see Dependability Analysis) via the arch_design attribute.

RST and Markdown Wrappers

When you want to combine a diagram with text, create an RST or Markdown file that embeds the diagram using the .. uml:: directive (RST) or the MyST equivalent.

RST wrapper example:

Static Architecture
-------------------

The following diagram shows the component structure of MySeooc.

.. uml:: MySeooc_StaticDesign.puml

Include both the wrapper file and the referenced .puml file in the same Bazel list — the build needs both:

architectural_design(
    name   = "my_arch",
    static = [
        "static_design.rst",          # wrapper with prose
        "MySeooc_StaticDesign.puml",  # diagram referenced by the wrapper
    ],
)

Rule Reference: architectural_design

For the complete architectural_design attribute reference, see architectural_design in the rule index.