9.9 KiB

layout title parent nav_order permalink
default Architecture notebook Architecture 2 floorplanner/project/architecture/notebook

Architecture Notebook

Avoid premature abstraction

Checklist {: .label .label-yellow }

An important checklist item to take into account for the Groundwork project is to always carefully consider whether abstractions to the software need to be introduced. The architecture patterns used in the platform come in many different variations, and when searching information online they might seem to be requirements.

Anti-patterns taken from Avoiding Premature Software Abstractions (via by Jonas Tulstrup:

  1. Responsibilities are abstracted too granularly
  2. Design patterns are used without real benefit
  3. Performance is optimized prematurely
  4. Low coupling is introduced everywhere

{: .danger }

Software literature likes to introduce abstractions. ONLY add them when absolutely necessary.

This diagram taken from the article says it all:

Premature abstraction architecture anti-pattern: Diagrams a before and after of refactoring an event-driven design to include way less moving parts

Align with Phoenix LiveView

Decision {: .label .label-green }

To-Update {: .label .label-yellow }

In all investigation of the Elixir ecosystem it becomes ever clearer how instrumental and powerful the Phoenix Framework really is. Hence Phoenix will be leveraged to maximum extent. This poses challenges as Phoenix is an extensive framework with many features. The Mix code generator are considered learning tools only, and may not fit the desired architecture patterns.

Phoenix has the concept of Contexts since version 1.3, but they should not be confused with DDD Bounded Contexts. This is discussed on the Elixir Forum in this topic.

At ElixirConf 2021 Jenny Shih presented a great talk Context Driven Development: Architect your Code with Phoenix Context (video) and example implementation that combines Clean / Hexagonal architecture, DDD bounded contexts and Phoenix contexts into the project structure.

We will use Jenny's work as a guidance for the clean architecture.

Elixir Umbrella app or not?

Decision {: .label .label-green }

To-Update {: .label .label-yellow }

  • The groundwork repository will be a monorepo for the core server and essential services (modeled as sub-domains).
    • Service Modules will contain their own independent bounded context(s), and depend on the core server. They are implemented in their own separate repository.
  • Umbrella Projects are frequently mentioned as a good separation for DDD Bounded Contexts.
  • However it looks like Umbrella Apps may be a better fit in the long run to separately model DDD Bounded Contexts and deploy Service Modules for system services that come bundled with Groundwork.

Clean architecture

Decision {: .label .label-green }

To-Update {: .label .label-yellow }

We apply the Clean Architecture pattern introduced by Uncle Bob. The pattern comes with an additional best-practice to create a descriptive top-level project folder structure, that is NOT organized along technical concepts, but instead reflects the domains being modeled.

Folder structure (example):

  ├– /groundwork
  |    ├– /[BoundedContext]                    # internal
  |    |    ├– /model                          #   domain
  |    |    |    ├– [AnAggregate].ex           #     aggregate root
  |    |    |    ├– [AnEntity]_entity.ex       #     entity
  |    |    |    └– [AValueObject]_value.ex    #     value object
  |    |    |    └– [AnAggregate]_store.ex     #     repository interface
  |    |    ├– /command                        #   commands
  |    |    ├– /event                          #   events
  |    |    ├– some_use_case.ex                #   use case
  |    |    ├– some_other_use_case.ex          #   ...
  |    ├– router.ex
  |    ├– event_store.ex
  |    └– /[BoundedContext]_external           # external
  └– /config

Here's a module structure for an AccountManagement bounded context:

Groundwork.AccountManagement.AccountManagement.create_account()  # use case

Create apps using Mix with:

mix new app_name --module AppModuleName --sup

Commanded architecture considerations

Ben Smith, Commdanded maintainer provides insight on various Commanded application architectures:

  1. One monolithic service with a single global Commanded application.
  2. One monolithic service containing many contexts, all sharing a single Commanded application.
  3. One monolithic service containing many contexts, each context using its own Commanded application.
  4. Many microservices with each service using its own Commanded application, and an additional Commanded application for integration.
  5. Multi-tenant service with each tenant using its own Commanded Application.

For the Core Server and the Service Modules that represent system services, the first monolithic architecture design suffices. This is also the best starting point for the MVP.

When applications are developed on top of the Groundwork service platform, i.e. as independent Service Modules, then option 3 should be considered first.

Bootstrapping social experiences

Decision {: .label .label-green }

To-Update {: .label .label-yellow }

There's a tight relationship between Taskweave and Groundwork. The Process is not just a static documentation artifact with developer guidelines and reference material. In addition ever more of the Process will be supported by tools and automation. Domain driven design is foundational to creating social experiences with Solidground, and is an ongoing activity during the entire project development lifecycle. After all, domains are ever evolving, and the application should facilitate that.

From this perspective offering a bootstrap project to help developers is not ideal. A bootstrap can be used only once. As soon as it is customized there's no way to regenerate boilerplate. This architecture decision is about adding a major component to the Solidground project: Floorplanner.


  • Keep modeling in sync of the codebase
  • Keep developers working on Solidground best-practices
  • Keep Taskweave core to the development method
  • Provide a baseline platform for automation tools

Living documentation

Not started {: .label .label-red }

To-Update {: .label .label-yellow }


Event driven architecture

Decision {: .label .label-yellow }

Open {: .label .label-grey }

Event Driven Architecture (EDA) forms the basis of Groundwork. The bounded contexts of a domain model communicate via Domain events. Where these are implemented in different Service Modules or invoked remotely over the Fediverse message exchange takes place.

EDA being central means that all supported Architecture Patterns generate events, i.e. all bounded contexts whether they are implemented as Service Wrapper, CRUD, CQRS or CQRS/ES.


Phoenix LiveView

Decision {: .label .label-yellow }

Open {: .label .label-grey }

Floorplanner is operated via a Phoenix LiveView user interface, but functionality can also be invoked by the Floorplanner API.

{: .issue}

Initially we focus on Phoenix LiveView UI. Headless operation support is on the roadmap.
See: housekeeping #19.


Ejectable apps

Decision {: .label .label-yellow }

Open {: .label .label-grey }

Uniform is an Elixir library that allows to develop a portfolio of apps as a monolith and then before deploying eject them into separate applications.

After investigating decision was to keep this Open before using the project, until more of the baseline for Floorplanner has been established. The Elixir library has potential, and the product concept of "Ejecting" for deployment fits well with Floorplanner's social experience design.

Important resources: