Previous post introduced solid foundation - aka hexagonal architecture - to build a project on, explaining how we should (re)think the way we design and craft a software. In this post we will see how one could leverage this pattern to support his daily life.
Workload split
One way to sketch backlog split is to identify internal parts in term of bounding contexts and known markers and to address them accordingly (in sequence or in parallel).
Starting by deconstructing your work, highlighting what need to be done (boxes) and where lands coupling (circles). Once done, it becomes pretty obvious that once you agree upon circles i.e., define contracts or interfaces, you will be able to parallelize the work. For example, once frozen, top-left blue circle aka the interface allows:
- One to provide implementation based on the interface.
- Another one to provide unit test, once again based on the single interface (no implementation needed to write the test).
- A last one to craft his view model leveraging the single interface (no implementation to compile, mocking the interface to run)
Those 3 activities can occur in parallel, speeding up the delivery pipeline. No more bottleneck.
Of course, it really shines for the bottom use case:
- Left green view model only needs the blue interface. It is not aware of downstream pipeline (right blue implementation and underlying magenta facilities). More than that, it does not care about it. And does not have to. You can still show case compelling workflow, mocking the blue interface, even if the whole pipeline is not shaped yet. Very valuable to refine UX with product owner, even if nothing really happen backstage.
- On the other side, magenta implementation of the blue SPI can be crafted without awareness of any user interface. Once again, it does not care about it. And does not have to. You can also show case through unit testing or other triggers. No need to wait to have an eye-candy up & running front-end to exercise it.
Once properly mastered, this way of splitting becomes a game changer to smoothly and smartly deliver your feature in an incremental fashion.
Workflow
All development have to obey timeline, so one of your daily work is to learn how to achieve the best Return of Investment. A real game changer aspects of hexagonal architecture is that it allows you - and even encourages you - to introduce delays in your decision making. In fact, it appears very often that you do not have all the answers when you start thinking about a problem to solve, and basically, it is just fine. Main idea when you face such situation is to provision placeholder, either API or SPI, and just move on e.g., start coding assuming that as some point they will be provisioned. For example, you need some persistence but cannot make decision right now if you should lead to SQL or document-based solution. Just postpone the choice, until you get more comfortable with the context, the use case, the technical stack, …
As for the mantra that one does not create a platform, but a platform emerges, same applies to your Core. Aggregating use cases, improving your knowledge, exercising your Core will sharpen and refine your needs and the contracts you obey. In the meanwhile you can leverage both Mock to simulate your SPI or simple ways to exercise your API such as component testing, swagger UI, …
You do not need an eye-candy UI or a monstrous backend infrastructure to assess your Core.
Your time is valuable. Invest it on your Core as it is the most sustainable piece.
You are likely to change the way you interact with it e.g., moving from WPF application to a Blazor one.
You are likely to change your persistence stack e.g., moving from file system to data base.
You are likely to upgrade your 3PP to a major version and its inherent breaking changes.
You are likely to remove a 3PP due to licensing modification or end of partnership.
You can assume 2 things: list is both endless and inescapable.
Question is not What or If but When.
But it is just fine as from now on your Core does not care. It is even not aware of those changes.
It can accommodate. It can survive. It can evolve. You are safe.
Boundaries
One crucial point is to grasp that our Core must be protected from outside unsafe world. Unsafe meaning w/o semantic. From a typed language point of view, we can sum it up as untyped or typed another way.
Main pitfalls to avoid from Presentation layer is to ingest primitives. Inputs should be as fast as possible converted into proper Core concepts such as Value Objects:
- Prevent Core to be contaminated e.g., by incorrect values.
- Enforce Fail Fast Principle, notifying Core user with compelling insights as soon as possible.
Main pitfalls to avoid from Infrastructure layer is to craft SPI from a 3PP point of view. SPI is crafted from the Core needs, assuming that someone at the end will serve this service:
- Prevent Core to be contaminated, either by 3PP domain or business logic.
- Reduce adherence to a given 3PP and ease future swap.
Naming
It is used to use smart naming for either assembly name or matching namespace to help identifying layer stack e.g.,:
Sticking to this convention, bounding contexts are properly clustered and materialized.
As every project is easily localizable, we decrease the cognitive overload and speed up the topic adoption phase.
Markers
Topics naturally have layer affinity. To ease adoption and decrease cognitive overload, it is used to introduce marker interfaces e.g., IService, IViewModel, IFactory, IEntity, IValueObject, IComponent, ISystem, … to materialize this affinity. Main objective is to stick to convention over configuration mantra. Every marker comes with its own invariant semantic to enforce application standard.
- IEntity & IValueObject comes from DDD and thus have different semantic for the notion of equality.
- Two value objects are the same if all of their fields are.
- Two entities are the same if their Id are, even if other fields aren’t.
- IViewModel contains only properties, commands & events. They leverage underlying - stateless if possible to ease Unit Testing - IService to perform computation and handle use cases.
- IFactory acts as object spawner, ensuring contextual guard clauses are respected. They throw otherwise, by convention, sticking to Fail Fast Principle.
Markers ease the classes classification and layer affiliation. Sticking to convention over configuration within projects help us to have a straightforward way of thinking, naming and tidying up stuff. It eases also a lot the registration phase when you are using IoC container to deal with Dependency Injection. Then every layer has their typical and well-known toddlers:
Closing
Hexagonal architecture is not the alpha & omega but it gives you strong tracks to make your code source more robust, resilient to change and thus sustainable. Beyond the architectural pattern, consider adhering to delay your decision making in case you feel you lack knowledge mantra.
Hexagonal architecture accommodates pretty well low space, so you can easily stick to it in a tiny sample by swapping projects with namespaces. Consider this workspace as a loft: no walls between rooms does not mean no space structure, you only need a bit more discipline and imagination.