DEV Community

BrycePC for AWS Community Builders

Posted on • Originally published at Medium

Building “The Better Store” an agile cloud-native ecommerce system on AWS — Part 3: DDD Tactical Patterns and App Architecture

Part Two of Building the Better Store described the use of DDD Strategic Patterns as tools for tackling complex requirements for a business system, by decomposing the problem domain to produce a high level design composed of decoupled ‘Bounded Contexts’; each of which may be considered as an initial blueprint for a Microservice.

This article continues from the conceptual view produced in part two, and becomes more technically-focused on implementation details using DDD Tactical Patterns.

Introducing DDD Tactical Patterns

DDD tactical patterns, also known as ‘model building blocks’, are used to help define static models for complex bounded contexts.
The main patterns and their relationships are illustrated as:


Figure 1: DDD Tactical Patterns

where each of the patterns may be described as below:


Figure 2: Table defining main tactical patterns, for designing a Bounded Context.

Example: Order Bounded Context

The Order bounded context was first introduced in my Part 2: Defining Defining DDD Strategic Patterns article, as representing a core subdomain responsible for managing orders and payments within The Better Store. Its DDD strategic design included the following:

A. BDD Features

  1. PurchaseProductsInCartFeature; order-related scenarios include: @ConfirmOrder; an Order consists of Products and their quantities in the cart, the Customer and associated email address, delivery address and shipping cost (of order contains physical products). The customer is directed to the payment system for completion here.
  2. ManageOrderFeature; order management scenarios including: @ViewOrder; allows details of a previously-created order to be retrieved from the system. @ViewOrderHistory; allows a list of previous orders created for a customer over the last 6 months to be retrieved.

B. Class Responsibility Collaboration


Figure 3: Collaboration Responsibility Card for the Order subdomain, illustrating relationships with other subdomains

Combining these strategic design outputs with the described tactical patterns, an initial draft high-level class diagram may be constructed as below:


Figure 4: High-level class diagram representing a static view of the Order bounded context, for a potential microservice implementation

Note that while this provides us with a good start for an object-oriented design of how we may wish to implement an Order microservice using an object-oriented language , we need to at this point consider an appropriate Application Architecture for structuring the service, using layering principles to help ensure the application can be easily extended and maintained into the future, while avoiding the potential Big Ball of Mud[7] anti-pattern! For this we will be using the Onion Architecture, as described next.


Application Architecture with Layering; Introducing Onion Architecture!

A layered application architecture is a standard technique used by software developers and application architects to structure application source code into abstract layers (or tiers); for example by splitting code into separate subdirectories, modules and/or namespaces within the application’s code repository based on their general concerns; such as presentation, business domain logic, and data access. An example of this topology is illustrated below:


Figure 5. Illustrating implementation and deployment of an n-tier application

This also promotes a top-down dependency model, whereby higher layers can only communicate with the layer above them; for example logic within the presentation layer cannot directly obtain data from the database via the data access layer; such queries must be via calls to the business layer.

Advantages of a layered architecture for realizing Separation of Concerns include:

  • Code complexity is reduced as is it organised within its area of concern. This allows application logic to be easier found and changed, with reduced risk of impacting other areas of the application. For example, making changes to the user interface may be performed with little or no change or regression testing being required for other application layers.
  • Such an architecture may even render it possible for an application’s entire user interface, or underlying database product to be replaced within an application, with little or no change being required to its business logic.

However, while the decomposition of an application into layers helps reduce its complexity, it operates at a high-level and does not necessarily include best practices for structuring code within the layers, or to align with artefact types modeled using Domain Driven Design. Each layer within a larger application can quickly become unwieldly and at risk of the ‘Big Ball of Mud’ as an application becomes larger if left unchecked without adoption of further decomposition and decoupling best practices and standards, such as Inversion of Control and _SOLID _by the development team. The Onion Architecture helps with the realization of these.


The Onion Architecture was first defined by Jeffrey Palermo [8] as a layered application dependency model, whereby outer layer components may be dependent on any of its lower-layer components, as illustrated in figure 6 below:


Figure 6: Layered application design with Onion Architecture

The architecture however exhibits the following differences to the n-tier layered architecture as described earlier:

  1. It promotes Inversion of Control (IoC) to provide loose or interchangeable coupling of components. In this respect, each layer/circle within its model encapsulates internal implementation details and exposes an interface for outer layers to consume.
  2. The inner components are comprised of domain entities and services as defined by our DDD tactical patterns to provide core business functionality. Also included are abstract interfaces, for example data access methods, as illustrated in the Application Core in figure 7. Their concrete methods however are implemented at the outermost infrastructure layer, using IoC. This is because the technology implemented for a database, or other external dependency such as HTTP Rest endpoints or technology-specific adapter should be agnostic to business domain components, and be able to be substituted with another product if needed. They may also be substituted with a mock component for automated testing purposes.
  3. Application services are often implemented to provide an additional decoupled layer above domain services, for example to:
  4. Serve as a proxy to requests to domain services, but with inclusion of additional functionality such as authentication/authorisation, or request/response object transformation, to satisfy system requirements. - Orchestrate calls to underlying domain services to meet specific use cases.
  5. The Infrastructure layer typically includes:
    • API servers such as REST API gateways, which manage and propagate externally-received requests to underlying application and domain service components.
    • Presentation components, such as a web user interface, which may be dependent on calls to application and domain services (as well as API’s as described above).
    • Repository components for accessing data stores; e.g. relational or NoSQL databases.
    • Adapters for accessing external dependencies; for example a REST HTTP client or AWS SQS client, both with bespoke security, logging and error handling requirements included.
    • Automated unit or integration tests. These can include dependency injection configurations which mock external dependencies, to provide fast and consistent test results within an isolated environment.

Details of IoC are out-of-scope of this article; further information of this and related SOLID principles which relate well to the technical implementation of The Better Store is well-described in R. Jansen’s web article: Implementing SOLID and the onion architecture in Node.js with TypeScript and InversifyJS [10]. The following class diagrams and sample code however provide an example of how InversifyJS may be used within our NodeJS+Typescript OrderService implementation to define interface bindings to concrete RestApi client class for our main application, and the same interface bindings to a mock Payment API client class for a corresponding automated test application.


Figure 7: Illustrating the use of a REST API Client adapter interface within the Application Core layer, and a choice of decoupled concrete classes within the Infrastructure layer which may be configured for the application via an IoC. For example, RestApiClient may be configured to be used by the application for production deployment; whereas a separate test build may be configured to use MockPaymentsApiClient to enable automated testing of the application independent of the Payments solution.

Using the Onion Architecture constructs described in the previous section, our Order microservice application architecture may be further refined as the following:


Figure 8: A high-level class diagram describing a potential Order microservice using the Onion Architecture, with swimlanes denoting its layers.

A complete AWS Serverless implementation of the architecture using Node.js with Typescript and Inversify for Dependency Injection will be described in the future Part Six article, however the following screenshot provides a taster of what we expect to come, in terms of its code scaffolding. Its code is available to view at: https://github.com/TheBetterStore/tbs-app-order.


Figure 9: Screenshot illustrating NodeJS+Typescript source code scaffolding to realise the described application architecture.

Coming next in Part Four: Building the Microservices Architecture with Cloud Native Design Patterns and AWS Services.

References

  1. “Domain Driven Design, Tackling Complexity in the Heart of Software”, Evans, E., Addison-Wesley (2003)
  2. “Patterns, Principles, and Practices of Domain-Driven Design”, Millett & Tune, Wiley & Sons (2015)
  3. “Practical Event-Driven Microservices Architecture”, Rocha, H., Apress (2022)
  4. “Building Microservices”, Newman, S., O’Reilly (2015)
  5. “Domain Driven Design & Microservices for Architects”, Sakhuja, R., Udemy (2021)
  6. “Microsoft Application Architecture Guide, 2nd ed”, Microsoft, _web _(2013)
  7. “Big Ball of Mud”, Foote & Yoder (University of Illinois), _web _(1999)
  8. “The Onion Architecture: part 1”, Palermo, J., _web _(2008)
  9. “DDD, Hexagonal, Onion, Clean, CQRS, … How I put it all together”, Graca, H, _web _(2017)
  10. “Implementing SOLID and the onion architecture in Node.js with TypeScript and InversifyJS”, Jansen, R., web (2018)
  11. “Onion Architecture Let’s slice it like a Pro”, Kapoor, R., web(2022)

Disclaimer: The views and opinions expressed in this article are those of the author only.

Top comments (0)