GitHub - FelixZY/specification-realization-assembly-bible: Documentation hub for my SRA software architecture

4 min read Original article ↗

SRA (Specification - Realization - Assembly) is an architectural pattern for software development created by Felix Zedén Yverås. It is an attempt to incorporate modern best practices with existing thoughts on software architecture, such as DDD and Hexagonal.

SRA relies heavily on dependency injection and is designed for both binaries and libraries. It natively supports projects with multiple outputs, making multi-platform development a breeze.

Core Philosophy: Right by Design

The core tenet of SRA is "Right by Design, Hard to Misalign", a translation based on the Swedish phrase lätt att göra rätt (lit. easy to do right).

By hiding implementations behind strict module boundaries and defaulting to private or internal visibility, the pattern makes sure the path of least resistance for a developer is to follow the its ruleset. Any deviations will cause a compilation error, making such changes clear and intentional.

The Structure

SRA divides code into a clear, three-tier hierarchy where dependencies flow from specification to assembly.

Specification (library modules) - "The What"
├── Models
├── Capabilities
└── Events

Realization (library modules) - "The How"
├── Handlers
├── Persistence (Sqlite, Postgres, etc.)
└── Presentation (Compose, GTK, QT, etc.)

Assembly (binary modules) "The Connection"
├── Web
├── CLI
├── Windows
├── MacOS
├── Linux (deb)
├── Linux (rpm)
├── Android
└── iOS

Dependencies have a clear direction from specification to assembly, making it difficult to interact with irrelevant code.

┌──────────────────────────────────────────────────────────┐
│                      SPECIFICATION                       │
│  (Models • Capabilities • Events)  <──┐                  │
└───────────────────────────────────────│──────────────────┘
                                        │
┌───────────────────────────────────────┴──────────────────┐
│                       REALIZATION                        │
│  (Handlers • Persistence • UI)     <──┤ (Implements Spec)│
└───────────────────────────────────────│──────────────────┘
                                        │
┌───────────────────────────────────────┴──────────────────┐
│                        ASSEMBLY                          │
│  (CLI • Mobile • Web • Windows)       (Binds Spec+Real)  │
└──────────────────────────────────────────────────────────┘

Specification - The What

The specification is the source of truth and heart of the system. This is the home of application-wide models and contracts describing the capabilities of the software.

The specification typically represents the public API of the software and is completely agnostic to its inner workings. This makes major changes, such as changing the database backend or even core logic, possible without major refactoring across the project. The use of contracts additionally allows for stronger test isolation as unit tests never need to rely on an actual implementation.

Realization - The How

The realization is where the magic happens. Typically split between "Application" and "Infrastructure" in DDD, the realization contains implementations of the contracts exposed by the specification. Note that most code in the realization layer is generally private or internal.

The general logic of the application typically goes into the "Handlers" submodule but effort should be taken to group code cleanly into minimal units. Subdirectories like Realization/Persistence/Sqlite and Realization/Persistence/Postgres can be a great way to achieve this!

Since the application capabilities live in the specification, the presentation layer becomes a simple realization of the specification. Rather than a bully that makes demands of the business logic - a common misstep when doing DDD - the UI becomes a plugin that's easy to substitute or amend as needs grow.

Assembly - The Connection

The assembly is where the final deliverable is hooked up and created. This could be a library, an executable or a web bundle. Or all of them.

The sole role of the assembly layer is to connect the specified capabilities of the application with the realized implementations using dependency injection. This makes it simple to swap realizations based on the current environment or output target (e.g. using Sqlite for the mobile application and Postgres for the server application).

Naming for Intent

Humans take the path of least resistance. Why use ICar when Car is right there? SRA takes this into account and uses naming conventions to reinforce architectural boundaries.

Use Car, not ICar

SRA relies heavily on indirection and exposes the contracts in the specification as its public API. The model/contract/interface is the "real" object as far as the rest of the system is concerned - let's make it the real object in developers' minds as well.

Use CarImpl, not Car

In SRA, implementations are background details. Nine times out of ten, developers should reach for the contract rather than the implementation. By moving the I prefix on the contract to an Impl suffix on the implementation, developers are steered toward the contract and only use the concrete implementation intentionally.