digital-restaurant

DDD. Event sourcing. CQRS. Modular. Java. Axonframework

View the Project on GitHub idugalic/digital-restaurant

projects/digital-restaurant Build Status

Customers use the website application to place food orders at local restaurants. Application coordinates a network of couriers who deliver the orders.

‘d-restaurant-backend’ is an example of an application that is built using Event Sourcing and CQRS. The application is written in Java (Kotlin is available on this branch), and uses Spring Boot. It is built using Axonframework, which is an application framework based on event sourcing and CQRS.

Domain

This layer contains information about the domain. This is the heart of the business software. The state of business objects is held here. Persistence of the business objects and possibly their state is delegated to the infrastructure layer

Business capabilities of ‘Digital Restaurant’ include:

As you try to model a larger domain, it gets progressively harder to build a single unified model for the entire enterprise. In such a model, there would be, for example, a single definition of each business entity such as customer, order etc. The problem with this kind of modeling is that:

Domain-driven design (DDD) avoids these problems by defining a separate domain model for each subdomain/component.

Subdomains are identified using the same approach as identifying business capabilities: analyze the business and identify the different areas of expertise. The end result is very likely to be subdomains that are similar to the business capabilities.

The Order (RestaurantOrder, CustomerOrder, CourierOrder) aggregate class in each domain model represent different term of the same ‘Order’ business concept.

The Restaurant component has a simpler view of an order aggregate (RestaurantOrder). Its version of an Order simply consist of a status and a list of line item, which tell the restaurant what to prepare. Additionally, we use event-driven mechanism called sagas to manage invariants between Restaurant aggregate and RestaurantOrder aggregate (e.g. Restaurant order should have only menu items that are on the Restaurant menu)

The Courier component has a different view of an order aggregate (CourierOrder). Its version of an Order simply consist of a status and a address, which tell the courier how and where to deliver the order. Additionally, we use saga to manage invariants between Courier aggregate and CourierOrder aggregate (e.g. Courier can deliver a limited number of orders)

We must maintain consistency between these different ‘order’ aggregates in different components/domains. For example, once the Order component has initiated order creation it must trigger the creation of RestaurantOrder in the Restaurant component. Similarly, if the restaurant rejects the order via the Restaurant component it must be rejected in the Order component. We will maintain consistency between components using sagas.

We use event sourcing to persist our event sourced aggregates as a sequence of events. Each event represents a state change of the aggregate. An application rebuild the current state of an aggregate by replaying the events.

Event sourcing has several important benefits:

Event sourcing also has drawbacks:

Organisation vs encapsulation

When you make all types in your application public, the packages are simply an organisation mechanism (a grouping, like folders) rather than being used for encapsulation. Since public types can be used from anywhere in a codebase, you can effectively ignore the packages.

The way Java types are placed into packages (components) can actually make a huge difference to how accessible (or inaccessible) those types can be when Java’s access modifiers are applied appropriately. Bundling the types into a smaller number of packages allows for something a little more radical. Since there are fewer inter-package dependencies, you can start to restrict the access modifiers.

For example, our Customer component classes are placed in one com.drestaurant.customer.domain package, with all classes marked as package protected (the default modifier). Public classes are placed in com.drestaurant.customer.domain.api and they are forming an API for this component. This API consist of commands and events only.

Traditional architectural style like ‘Layered architecture’ would create more packages in your component. For example, com.drestaurant.customer.domain.dao and com.drestaurant.customer.domain.service. This would force you to make some of the DAO classes public and make them part of the API for that component, which is wrong.

Application

This is a thin layer which coordinates the application activity. It does not contain business logic. It does not hold the state of the business objects

Monolith

Application exposes capabilities of our ‘domain’ via the REST API component that is responsible for

Event listener is a central component. It consumes events, and creates materialized views (projections) of aggregates. This makes querying of event-sourced aggregates easy.

‘Command’ API

1. Create new Restaurant
curl -X POST --header 'Content-Type: application/json' --header 'Accept: */*' -d '{
  "menuItems": [
    {
      "id": "id1",
      "name": "name1",
      "price": 100
    }
  ],
  "name": "Fancy"
}' 'http://localhost:8080/api/command/restaurant/createcommand'
2. Create/Register new Customer
curl -X POST --header 'Content-Type: application/json' --header 'Accept: */*' -d '{
  "firstName": "Ivan",
  "lastName": "Dugalic",
  "orderLimit": 1000
}' 'http://localhost:8080/api/command/customer/createcommand'
3. Create/Hire new Courier
curl -X POST --header 'Content-Type: application/json' --header 'Accept: */*' -d '{
  "firstName": "John",
  "lastName": "Doe",
  "maxNumberOfActiveOrders": 20
}' 'http://localhost:8080/api/command/courier/createcommand'
4. Create/Place the Order
curl -X POST --header 'Content-Type: application/json' --header 'Accept: */*' -d '{
  "customerId": "CUSTOMER_ID",
  "orderItems": [
    {
      "id": "id1",
      "name": "name1",
      "price": 100,
      "quantity": 0
    }
  ],
  "restaurantId": "RESTAURANT_ID"
}' 'http://localhost:8080/api/command/order/createcommand'

Note: Replace CUSTOMER_ID and RESTAURANT_ID with concrete values.

5. Restaurant marks the Order as prepared
curl -X POST --header 'Content-Type: application/json' --header 'Accept: */*' 'http://localhost:8080/api/command/restaurant/order/RESTAURANT_ORDER_ID/markpreparedcommand'

Note: Replace RESTAURANT_ORDER_ID with concrete value.

6. Courier takes/claims the Order that is ready for delivery (prepared)
curl -X POST --header 'Content-Type: application/json' --header 'Accept: */*' 'http://localhost:8080/api/command/courier/COURIER_ID/order/COURIER_ORDER_ID/assigncommand'

Note: Replace COURIER_ID and COURIER_ORDER_ID with concrete values.

7. Courier marks the Order as delivered
curl -X POST --header 'Content-Type: application/json' --header 'Accept: */*' 'http://localhost:8080/api/command/courier/order/COURIER_ORDER_ID/markdeliveredcommand'

‘Query’ API

Application is using an event handler to subscribe to all interested domain events. Events are materialized in denormalized SQL database schema.

REST API for browsing the materialized data:

curl http://localhost:8080/api/query

Microservices

Development

This project is driven using Maven.

Clone

$ git clone https://gitlab.com/d-restaurant/d-restaurant-backend.git

Build

$ cd d-restaurant-backend
$ ./mvnw clean install

Run monolith

$ cd d-restaurant-backend/drestaurant-apps/drestaurant-monolith
$ ../../mvnw spring-boot:run

Continuous delivery

We have one deployment pipeline for all applications and libraries within this repository. In addition, all projects in the repository share the same dependencies. Hence, there are no version conflicts because everyone has to use the same/the latest (SNAPSHOTS) version. And you don’t need to deal with a private NPM (JavaScript) or Maven (Java) registry when you just want to use your own libraries. This setup and project structure is usually addressed as a monorepo.

Technology

Language

Frameworks and Platforms

Continuous Integration and Delivery

Infrastructure and Platform (As A Service)

References and further reading

Inspired by the book “Microservices Patterns” - Chris Richardson