Application Structure Design
By Mark Hissink Muller
Faced with the question how to design the structure of a new custom software system, we present an approach based on a fictitious example of the Amazon web store.
Our application will have users, receive messages and have batch jobs. It will integrate with external systems and use a database. How should it be structured to address all concerns in the best possible location?
Approach
The essence of our approach lies in recognition of the different ‘areas of responsibility’ and the organisation of these areas along a namespace hierarchy.
Based on this idea, we:
- Draw and name the outer boundaries of responsibility (
com.amazon.store.
); - Within this, we mimic the outside world in technical layering;
- users (
web.
), messages (message.
) and batch jobs (batch.
) interact with the system, - the system itself interacts with external systems (
integration.
) and the database (persistence.
);
- users (
- Add areas for the services (
service.
) and the core domain (domain.
); - Within the technical layers, add the functional areas of responsibility, e.g. for the billing service (
service.billing
), for integration with American Express (integration.amex
) or for persistence of the customer data (persistence.customer
).
The different steps of the approach are presented in the following diagrams.
1. Draw and name the outer boundaries
2. Mimic the outside world
3. Add the service and domain layers
4. Complete the functional areas
Reflections
The areas of responsibility allow us to ‘position’ functionality in classes in context of how they should/will be used in the different application layers, even when the actual implementation might based on modules with a functional organisation.
Arguably the largest benefit of this approach is communicative value it provides during early stages of the design. Using this approach, problems in design, unfortunate naming or thinking quickly become apparent from suboptimal package names.
The adherence to this design can be measured and enforced by a static analysis tool (e.g. ArchUnit) that ensures no calls are allowed e.g. from the persistence layer back to the service layer.
One of the most underappreciated topics in architecture is the question what happens where. You cannot answer or discuss this question without a shared mental model.