Background
Atomiv Architecture template is based on best practices in architecture - focusing on Domain Driven Design (DDD), Use Case Driven Design (UCDD) and Test Driven Design (TDD) as the guiding drivers for system design. When designing the architecture template, we took into account world-leading architectures including Clean Architecture, Hexagonal Architecture, Onion Architecture. For an introduction regarding these architecture concepts, see https://atomiv.org/framework/software-architecture/clean-architecture.
Data Driven Design
But, let’s take a step back and ask - why this architecture? Historically, the common common way of doing architecture was the traditional layered architecture. in this way, the developers firstly start with ERD design and setting up the database and the the Data Access Layer (aka Persistence Layer). Then after that, developers implement the Business Logic Layer (aka Business Layer), which depends on the Data Access Layer. And finally, we have the UI (aka Presentation Layer), which depends on the Business Logic Layer. So let’s trace the dependencies, when the user makes some action on the UI, then the UI calls the Business Logic Layer, which depends on the Data Access Layer, which depends on the Database… So ultimately, everything transitively depends on the Database. This approach is known as the Data Driven Design, because the data (database) is the centre of our focus. And on small to medium projects, with low business complexity, and where most operations are just CRUD, then this is just fine.
To give an example of Data Driven Design. Suppose we’re making a web shop. In that case, we’d firstly start with choosing a database, let’s say we choose a popular relational database. Then we’d be drawing up the ERD, thinking about the database entities and the relationships / foreign keys - so we’d be thinking about entities Order, OrderItem, Product, Customer, about their primary keys (Id) and the foreign keys(e.g. OrderItem has foreign key OrderId). After the ERD, we’d proceed to implement the Data Access Layer, probably using an ORM, whereby we’d have a mirror of the database entities and their relationships. After that’s done, then we start thinking about the logic, so in the Business Logic Layer we implement some logic about calculating the order total. Finally, in the UI, we make some screens to that users can create orders.
But what happens when we’re working with larger projects, where business logic becomes more and more complex over time? What happens when we want to make more testable and maintainable applications? And what about the issues when data access techniques (and corresponding ORM’s) change every few years, when we’re stuck with older technology because we cannot migrate it due to the data access layer? What happens when we realize that we cannot unit test logic because Business Layer depends on the Data Layer? What about the case when we cannot effectively parallelize development because firstly everyone’s waiting for the Data Layer before they can implement the Business Logic Layer, and then the UI cannot be done until the Business Layer is done…
Domain Driven Design
For these kinds of larger and more complex projects - this is where the traditional Data Driven Design becomes a maintenance nightmare. For this reason, architectures evolved (Clean Architecture, Hexagonal Architecture, Onion Architecture) which put the Domain (and not the Data!) at the centre of the system. Here the design process is completely different. We don’t start off with the database, we start off with the domain (spoiler alert: we actually forget about the database - until the end).
So, using the example of the web shop, we’d have meetings with the client to find out about their business model and their business processes. So the client tells that they have a Product catalogue, the products they’re selling. Their Customers want to make Orders. Firstly the Customer makes a draft Order, then they submit the Order, and then afterwards they ship the Order. Also, the way the total Order amount is computed is by summing up the totals per order item (price multipled by quantity) and then depending on our Customer (whether it’s a VIP or regular customer) we apply some discounts. But, also in addition to that, if it’s the Customer’s birthday, they get a special voucher… And so on, the discussion between the Business and IT people continues, with questions back and forth, and everyone getting a shared understanding of the business domain.
So now when the developers starting working on implementation, they start with translating the domain into code. So during this modelling, they identify the entities - Customer, Product, Order. These entities have an identity which we call CustomerIdentity, ProductIdentity, OrderIdentity. The entities will also have some methods, for example the Order has methods such as Submit(), Ship(), GetTotal()… Here’s the developers apply Object Oriented Programming (OOP). As the developers, are doing this, they’re also writing unit tests to test the logic, for example testing that Submit() can only be called for orders which are in draft status, and testing the computation logic for the order total. Similarly, unit tests are written for any other business logic.
But wait a second, we’ve started development without a database? Without an ERD? Yes, exactly, we started with modelling just the domain, based on what we learnt from the domain experts during our requirements analysis. Now, we do acknowledge the existence of some kind of persistence mechanism, but we don’t care about it’s implementation. So, we add ICustomerRepository, IProductRepository, IOrderRepository interfaces. Examples of methods inside IOrderRepository are GetOrder(OrderIdentity id), AddOrder(Order order), UpdateOrder(Order order). At this moment, we’re only modelling the interfaces to the persistence mechanism - the repository interfaces. But what kind of database are we going to be using, will it be MySQL, or Oracle, or MS SQL Server? What’s the ERD? Will we be using ORM or writing SQL queries or using stored procedures? Or perhaps will we be using NoSQL databases like MongoDB due to performance? That’s all just an implementation detail, we’re not thinking about that.
So, after we’ve modelled the domain, consisting of domain entities (Customer, Product, Order) and repositories (ICustomerRepository, IProductRepository IOrderRepository) then we think about the use cases. What tasks does the user want to accomplish by using our system? Based on further requirements meetings with out client, we discover that the use cases are: RegisterCustomer, PromoteCustomerToVip, ListProduct, UnlistProduct, CreateDraftOrder, SubmitOrder, ShipOrder, etc. For each of those use cases, we write have the request (i.e. input), the handling of the use case, and finally the response (i.e. output). During the handling of the use case, we delegate to the domain for processing. For example, the SubmitOrder use case calls the IOrderRepository to get an order by OrderIdentity, then calls the .Submit() method on the order, and finally calls the .Update(Order order) method on the IOrderRepository. As we’re doing this, we’re able to write unit tests to test the logic within the use cases - because everything is still working in memory, without any database yet.
Finally, we come to thinking about Infrastructure - about the actual implementation for the Database, and anything else that’s external (File system, System clock, Networking, etc.). This is where we might for now decide to use a specific database (e.g. MySQL) and specific way of accessing it (e.g. using ORM, or raw SQL queries, etc.) and implement the repository interfaces. When the application starts up, the dependency injection container is setup to inject the concrete implementations of the repositories, and we’re ready for writing integration tests.