Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/ttulka/ddd-example-ecommerce

Domain-driven design example in Java with Spring framework
https://github.com/ttulka/ddd-example-ecommerce

architecture ddd design domain-driven-design event-driven example hexagonal-architecture high-cohesion java low-coupling modular-monolith ood oop rich-domain-model screaming-architecture service-oriented-architecture services soa spring spring-boot

Last synced: about 22 hours ago
JSON representation

Domain-driven design example in Java with Spring framework

Awesome Lists containing this project

README

        

# DDD Example Project in Java: eCommerce

The purpose of this project is to provide a sample implementation of an e-commerce product following **Domain-Driven Design (DDD)** and **Service-Oriented Architecture (SOA)** principles.

Programming language is Java with heavy use of Spring framework.

```sh
# build
./mvnw clean install

# run
./mvnw spring-boot:run

# open in browser http://localhost:8080
```

## Table of Contents

- [Domains](#domains)
+ [Core Domain](#core-domain)
+ [Supporting Subdomains](#supporting-subdomains)
+ [Event Workflow](#event-workflow)
+ [Services Dependencies](#services-dependencies)
- [Architectural Overview](#architectural-overview)
+ [Screaming Architecture](#screaming-architecture)
+ [Packaging](#packaging)
+ [Assembling](#assembling)
+ [Anatomy of a Service](#anatomy-of-a-service)
- [Conclusion](#conclusion)
+ [Where to Next](#where-to-next)

## Domains

Several [Business Capabilities][vcha] have been identified:

[vcha]: http://bill-poole.blogspot.com/2008/07/value-chain-analysis.html

### Core Domain

- **Sales**
- put a product for sale
- categorize a product
- update a product
- change a product price
- validate an order
- place an order

### Supporting Subdomains

- **Warehouse**
- stack goods
- fetch goods for shipping

- **Billing**
- collect a payment

- **Shipping**
- dispatch a delivery

Later, we can think about more supporting domains (not implemented in this project):

- **Marketing**
- discount a product
- promote a product

- **User Reviews**
- add a product review

- **Customer Care**
- resolve a complain
- answer a question
- provide help
- loyalty program

The e-commerce system is a web application using a **Portal** component implementing the [Backends For Frontends (BFF)][bff] pattern.

The idea of [Microfrontends][microf] is implemented in an [alternative branch](https://github.com/ttulka/ddd-example-ecommerce/tree/microfrontend).

[bff]: https://samnewman.io/patterns/architectural/bff/
[microf]: https://martinfowler.com/articles/micro-frontends.html

### Event Workflow

The communication among domains is implemented via events:

![Event Workflow](doc/event-workflow.png)

When the customer places an order the following process starts up (the happy path):

1. Shipping prepares a new delivery.
1. Sales creates a new order and publishes the `OrderPlaced` event.
1. Shipping accepts the delivery.
1. Billing collects payment for the order and publishes the `PaymentCollected` event.
1. Warehouse fetches goods from the stock and publishes the `GoodsFetched` event.
1. Shipping dispatches the delivery and publishes the `DeliveryDispatched` event.
1. Warehouse updates the stock.

There is only the basic "happy path" workflow implemented with a big room for improvement, for example when Shipping doesn't get bot Events within a time period, the delivery process should be cancelled etc..

### Services Dependencies

Services cooperate together to work out the Business Capabilities: sale and deliver goods.

The actual dependencies come only from Listeners which fulfill the role of the Anti-Corruption Layer and depend only on Domain Events.

![Event and Listener](doc/event-listener.png)

Events contain no Domain Objects.

For communication across Services an Event Publisher abstraction is used, located in the package `..ecommerce.common.events`. The interface is an Output Port (in the Hexagonal Architecture) and as a cross-cutting concern is its implementation injected by the Application.

## Architectural Overview

While no popular architecture ([Onion][onion], [Clean][clean], [Hexagonal][hexagonal], [Trinity][trinity]) was strictly implemented, the used architectural style follows principles and good practices found over all of them.
- Low coupling, high cohesion
- Implementation hiding
- Rich domain model
- Separation of concerns
- The Dependency Rule

The below proposed architecture tries to solve one problem often common for these architectural styles: [exposing internals of objects](https://blog.ttulka.com/object-oriented-design-vs-persistence) and breaking their encapsulation. The proposed architecture employs full object encapsulation and rejects anti-patterns like Anemic Domain Model or JavaBean. An Object is a solid unit of behavior. A Service is an Object on higher level of architectural abstraction.

[onion]: http://jeffreypalermo.com/blog/the-onion-architecture-part-1
[clean]: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
[hexagonal]: https://alistair.cockburn.us/hexagonal-architecture/
[trinity]: https://github.com/oregor-projects/trinity-demo-java

### Screaming Architecture

The architecture "screams" its intentions just by looking at the code structure:
```
..ecommerce
billing
payment
sales
category
order
product
shipping
delivery
warehouse
```

Going deeper the technical concepts are visible too:
```
..ecommerce
billing
payment
jdbc
listeners
rest
```

### Packaging

As shown in the previous section, the code is structured by the domain together with packages for technical concerns (`jdbc`, `rest`, `web`, etc.).

Such a packaging style is the first step for a further modularization.

The semantic of a package is following: `company.product.domain.service.[entity|impl]`, where `entity` and `impl` are optional. Full example: `com.ttulka.ecommerce.billing.payment.jdbc`.

### Assembling

While a physically monolithic deployment is okay for most cases, a logically monolithic design, where everything is coupled with everything, is evil.

To show that the Monolith architectural pattern is not equal to the Big Ball Of Mud, a modular monolithic architecture was chosen as the start point.

The services can be further cut into separate modules (eg. Maven artifacts) by feature:
```
com.ttulka.ecommerce:ecommerce-application
com.ttulka.ecommerce.sales:catalog-service
com.ttulka.ecommerce.sales:cart-service
com.ttulka.ecommerce.sales:order-service
com.ttulka.ecommerce.billing:payment-service
com.ttulka.ecommerce.shipping:delivery-service
com.ttulka.ecommerce.warehouse:warehouse-service
```

Or by [component](https://blog.ttulka.com/package-by-component-with-clean-modules-in-java):
```
com.ttulka.ecommerce.billing:payment-domain
com.ttulka.ecommerce.billing:payment-jdbc
com.ttulka.ecommerce.billing:payment-rest
com.ttulka.ecommerce.billing:payment-events
com.ttulka.ecommerce.billing:payment-listeners
```

In detail:
```
com.ttulka.ecommerce.billing:payment-domain
..billing
payment
Payment
PaymentId
CollectPayment
FindPayments
com.ttulka.ecommerce.billing:payment-jdbc
..billing.payment.jdbc
PaymentJdbc
CollectPaymentJdbc
FindPaymentsJdbc
com.ttulka.ecommerce.billing:payment-rest
..billing.payment.rest
PaymentController
com.ttulka.ecommerce.billing:payment-events
..billing.payment
PaymentCollected
com.ttulka.ecommerce.billing:payment-listeners
..billing.payment.listeners
OrderPlacedListener
```

Which can be brought together with a Spring Boot Starter, containing only Configuration classes and dependencies on other modules:
```
com.ttulka.ecommerce.billing:payment-spring-boot-starter
..billing.payment
jdbc
PaymentJdbcConfig
listeners
PaymentListenersConfig
META-INF
spring.factories
```

Note: Events are actually part of the domain, that's why they are in the package `..ecommerce.billing.payment` and not in `..ecommerce.billing.payment.events`. They are in a separate module to break the build cyclic dependencies: a dependent module (Listener) needs to know only Events and not the entire Domain.

See this approach in an alternative branch: [modulith](https://github.com/ttulka/ddd-example-ecommerce/tree/modulith).

### Anatomy of a Service

**[Service](http://udidahan.com/2010/11/15/the-known-unknowns-of-soa/)** is the technical authority for a specific business capability.
- There is a one-to-one mapping between a Bounded Context and a Subdomain (ideal case).
- A Bounded Context defines the boundaries of the biggest services possible.
- A Bounded Context can be decomposed into multiple service boundaries.
- For example, Sales domain contains Catalog, Cart and Order services.
- A service boundaries are based on service responsibilities and behavior.
- A service is defined by its logical boundaries, not a physical deployment unit.

**Application** is a deployment unit. A monolithic Application can have more Services.
- Bootstrap (application container etc.).
- Cross-cutting concerns (security, transactions, messaging, logging, etc.).

![Application and Services](doc/application-services.png)

**Configuration** assemblies the Service as a single component.
- Has dependencies to all inner layers.
- Can be implemented by Spring's context `@Configuration` or simply by object composition and Dependency Injection.
- Implements the Dependency Inversion Principle.

**Gateways** create the published API of the Service.
- Driving Adapters in the Hexagonal Architecture.
- REST, SOAP, or web Controllers,
- Event Listeners,
- CLI.

**Use-Cases** are entry points to the service capabilities and together with **Entities** form the _Domain API_.
- Ports in the Hexagonal Architecture.
- No implementation details.
- None or minimal dependencies.

_Domain Implementation_ fulfills the Business Capabilities with particular technologies.
- Driven Adapters in the Hexagonal Architecture.
- Tools and libraries,
- persistence,
- external interfaces access.

Source code dependencies point always inwards and, except Configuration, are strict: allows coupling only to the one layer below it (for example, Gateways mustn't call Entities directly, etc.).

![Service Anatomy](doc/service-anatomy.png)

#### Example of a Service Anatomy

As a concrete example consider the Business Capability to find payments in Billing service:

- Application is implemented via Spring Boot Application.
- `PaymentJdbcConfig` configures the JDBC implementations for the Domain.
- Gateway is implemented as a REST Controller.
- Use-Case interface `FindPayments` is implemented with `PaymentsJdbc` in Use-Cases Implementation.
- Entity `Payment` is implemented with `PaymentJdbc` in Entities Implementation.

![Service Anatomy](doc/service-anatomy-example.png)

There is no arrow from Configuration to Gateways because `PaymentController` is annotated with Spring's `@Component` which makes it available for component scanning the application is based on. This is only one possible approach. Another option would be to put the Controller as a Bean into the Configuration, etc..

## Conclusion

The goal of this project is to demonstrate basic principles of Domain-Driven Design in a simple but non-trivial example.

For the sake of simplicity a very well-known domain (e-commerce) was chosen. As every domain differs in context of business, several assumption must have been made.

Although all fundamental use-case were implemented, there is still a room for improvement. Cross-cutting concerns like authentication, authorization or monitoring are not implemented.

### Where to Next

Check out the alternative branches and repos to see additional concepts and technologies in action:

- [Modulith](https://github.com/ttulka/ddd-example-ecommerce/tree/modulith): A separate Maven module per service.
- [Microfrontends](https://github.com/ttulka/ddd-example-ecommerce/tree/microfrontend): Service Web Components as part of the service codebase.
- [Microservices](https://github.com/ttulka/ddd-example-ecommerce-microservices): Deployments with Docker and Kubernetes.
- [Kotlin](https://github.com/ttulka/ddd-example-ecommerce-kotlin): The same project again, this time in Kotlin.