Skip to content

☕A practical and imaginary food delivery microservices, built with java spring, domain-driven design, cqrs, vertical slice architecture, event-driven architecture, and the latest technologies.

License

Notifications You must be signed in to change notification settings

mehdihadeli/spring-food-delivery-microservices

Repository files navigation

🍔 Spring Food Delivery Microservices

Commitizen friendly

Open in GitHub Codespaces

Spring Food Delivery Microservices is a fictional food delivery microservices, built with .Net Core and different software architecture and technologies like Microservices Architecture, Vertical Slice Architecture , CQRS Pattern, Domain Driven Design (DDD), Event Driven Architecture. For communication between independent services, we use asynchronous messaging with using rabbitmq on top of spring amqp, and sometimes we use synchronous communication for real-time communications with using REST calls.

Note

This application is not business oriented and my focus is mostly on technical part, I just want to implement a sample with using different technologies, software architecture design, principles and all the thing we need for creating a microservices app.

Warning

This project is in progress. I add new features over the time. You can check the Release Notes.

⭐ Support

If you like feel free to ⭐ this repository, It helps out :)

Thanks a bunch for supporting me!

Table of Contents

Features

  • ✅ Using Spring MVC as a Web Framework.
  • ✅ Using Microservices and Vertical Slice Architecture as a high level architecture
  • ✅ Using Event Driven Architecture on top of RabbitMQ Message Broker and Spring AMQP library
  • ✅ Using Domain Driven Designin most of services like Customers, Catalogs, ...
  • ✅ Using CQRS Pattern on top of java-mediator package and splitting read models and write models
  • ✅ Using Structured logging with slf4j and log4j2 and using Console Appender to write logs to the console and OpenTelemetryAppender to send logs to grafana loki and kibana
  • ✅ Using Outbox Pattern for all microservices for Guaranteed Delivery or At-least-once Delivery
  • ✅ Using Inbox Pattern for handling Idempotency in reciver side and Exactly-once Delivery
  • ✅ Using Fluent Validation and Spring Validation and Validation Pipeline Behavior on top of java-mediator package
  • ✅ Using Postgres on top of Spring Data JPA for write database as relational DB and MongoDB on top of Spring Data MongoDB for read database
  • ✅ Using docker and docker-compose for deployment
  • ✅ Using Spring Cloud Gateway as reverse proxy and API Gateway
  • ✅ Using different type of tests like Unit Tests, Integration Tests, End-To-End Tests and testcontainers for testing in isolation
  • ✅ Using OpenTelemetry Spring Boot starter which collects some packages from open-telemetry/opentelemetry-java-instrumentation/ and open-telemetry/opentelemetry-java to collect Logs, Metrics and Traces and sending telemetry data to different exporters like prometheus, tempo, loki, jaeger, kibana through opentelemetry collector and showing them in grafana dashboard
  • ✅ Using Unit Testing for testing small units and mocking our dependencies with Mockito.
  • ✅ Using End-To-End Testing and Integration Testing for testing features with all dependencies using testcontainers.
  • ✅ Using Springdoc Openapi for generating OpenAPI documentation in Spring Boot.

Plan

This project is in progress, new features will be added over time.

Feature Architecture Pattern Status CI-CD
Building Blocks - ✅ Completed -
API Gateway - ✅ Completed -
Catalogs Service Domain Driven Design ✅ Completed -
Identity Service Using Keycloak ✅ Completed -
Users Service Data Centric Design 👷 In-Progress -
Customers Service Domain Driven Design 👷 In-Progress -
Orders Service Event Sourcing ❌ Not Started -

Technologies - Libraries

  • ✔️ Spring Boot - Framework for building Java applications with pre-configured defaults and embedded server support.
  • ✔️ Spring-Cloud-Gateway - An API Gateway built on Spring Framework and Spring Boot providing routing and more.
  • ✔️ Spring AMQP - Simplifies messaging using RabbitMQ with declarative configuration and templates.
  • ✔️ Spring Data JPA - Enhances JPA with repository abstractions and advanced query capabilities.
  • ✔️ Spring Data MongoDB - Provides seamless MongoDB integration with Spring-based applications.
  • ✔️ Spring Security - Comprehensive security framework for authentication and authorization in Java applications.
  • ✔️ Springdoc OpenAPI - Automatically generates OpenAPI 3 documentation for Spring Boot projects.
  • ✔️ Swagger Core - Core library for building and consuming Swagger-compliant APIs.
  • ✔️ Opentelemetry-Java - OpenTelemetry Java SDK and using its BOM.
  • ✔️ Opentelemetry-Java-Instrumentation - OpenTelemetry auto-instrumentation and instrumentation libraries for Java and using its BOM.
  • ✔️ OpenTelemetry Collector - Vendor-agnostic way to receive, process and export telemetry data.
  • ✔️ Flyway - Database migration tool for version-controlled and repeatable schema changes.
  • ✔️ JPA Buddy - Productivity tool for working with JPA and Hibernate, simplifying development and debugging.
  • ✔️ ULID Creator - Library for generating UUIDs in various formats and versions.
  • ✔️ QueryDSL - Enables type-safe queries for JPA, SQL, and other persistence layers.
  • ✔️ Testcontainers - Provides lightweight, disposable Docker containers for testing purposes.
  • ✔️ Mockito - Popular mocking framework for writing clean, maintainable unit tests in Java.
  • ✔️ JUnit - Essential testing framework for Java developers, supporting unit and integration testing.
  • ✔️ Rest-Assured - Java DSL for easy testing of REST services.
  • ✔️ Spotbugs - SpotBugs is FindBugs' successor. A tool for static analysis to look for bugs in Java code.
  • ✔️ Mapstruct - An annotation processor for generating type-safe bean mappers.
  • ✔️ Wiremock - A tool for mocking HTTP services.
  • ✔️ Datafaker - Generating fake data for the JVM (Java, Kotlin, Groovy) has never been easier!
  • ✔️ Guava - Google core libraries for Java.
  • ✔️ Hibernate-Metamodel-Generator - Annotation Processor to generate JPA 2 static metamodel classes.
  • ✔️ Spotless - Keep your code spotless.
  • ✔️ Palantir-Java-Format - A modern, lambda-friendly, 120 character Java formatter.

The Domain And Bounded Context - Service Boundary

TODO

Application Architecture

The bellow architecture shows that there is one public API (API Gateway) which is accessible for the clients and this is done via HTTP request/response. The API gateway then routes the HTTP request to the corresponding microservice. The HTTP request is received by the microservice that hosts its own REST API. Each microservice is running within its own AppDomain and has directly access to its own dependencies such as databases, files, local transaction, etc. All these dependencies are only accessible for that microservice and not to the outside world. In fact microservices are decoupled from each other and are autonomous. This also means that the microservice does not rely on other parts in the system and can run independently of other services.

Microservices are event based which means they can publish and/or subscribe to any events occurring in the setup. By using this approach for communicating between services, each microservice does not need to know about the other services or handle errors occurred in other microservices.

In this architecture we use CQRS Pattern for separating read and write model beside of other CQRS Advantages. Here for now I don't use Event Sourcing for simplicity but I will use it in future for syncing read and write side with sending streams and using Projection Feature for some subscribers to syncing their data through sent streams and creating our Custom Read Models in subscribers side.

Here I have a write model that uses a postgres database for handling better Consistency and ACID Transaction guaranty. beside o this write side I use a read side model that uses MongoDB for better performance of our read side without any joins with using some nested documents, also better scalability with some good scaling features in MongoDB.

For syncing our read side and write side we have 2 options with using Event Driven Architecture (without using events streams in event sourcing):

  • If our read sides are in same service, during saving data in write side and in the same transaction, we save a Internal Command record in our PersistMessage storage (like something we do in outbox pattern) and after committing write side, our MessagePersistenceBackgroundService and MessagePersistenceServiceImpl reads unsent internal commands and sends them to their corresponding internal command handlers in same service and these handlers can save their read models in our MongoDb database as a read side.

  • If our read sides are in another services we publish an integration event (with saving this message in the outbox) after committing our write side, all of our subscribers or consumers can get these events and save them in their read models (MongoDB).

All of these are optional in a application and we should only use what the service requires, For example, if the service does not need to use DDD because the business logic is very simple, and it is mostly CRUD we can use data centric architecture or If our application is not Task based instead of CQRS and separating read side and write side, we can just use a simple CRUD based approach.

Here I used Outbox for Guaranteed Delivery and can be used as a landing zone for integration events before they are published to the message broker.

Outbox pattern ensures that a message was sent (e.g. to a queue) successfully at least once. With this pattern, instead of directly publishing a message to the queue, we put it in the temporary storage (e.g. database table) for preventing missing any message and some retry mechanism in any failure (At-least-once Delivery). For example When we save data as part of one transaction in our service, we also save messages (Integration Events) that we want to process later in another microservices. The list of messages to be processed is called a PersistMessage with a MessageDeliveryType which can be Outbox, Inbox and InternalCommand and a MessageStatus which can be Stored and Delivered. The MessagePersistenceService service is responsible for doing this message processing internally.

Also we have a background service MessagePersistenceBackgroundService that periodically checks the our PersistMessages in the database and try to send the messages to the broker with using our MessagePersistenceService service. After it gets confirmation of publishing (e.g. ACK from the broker) it marks the message as processed to avoid resending and duplication. However, it is possible that we will not be able to mark the message as processed due to communication error, for example broker is unavailable. In this case our MessagePersistenceBackgroundService try to resend the messages that not processed and it is actually At-Least-Once delivery. We can be sure that message will be sent once, but can be sent multiple times too! That’s why another name for this approach is Once-Or-More delivery. We should remember this and try to design receivers of our messages as Idempotents, which means:

A message that has the same effect whether it is received once or multiple times. This means that a message can safely be resent without causing any problems even if the receiver receives duplicates of the same message.

For handling Idempotency and Exactly-once Delivery in consumers side, we can use Inbox Pattern.

This pattern is similar to Outbox Pattern. It’s used to handle incoming messages (e.g. from a queue) for unique processing of a single message only once (even with executing multiple time). Accordingly, we have a table in which we’re storing incoming messages. Contrary to outbox pattern, we first save the messages in the database, then we’re returning ACK to queue. If save succeeded, but we didn’t return ACK to queue, then delivery will be retried. That’s why we have at-least-once delivery again. After that, an inbox background process runs and will process the inbox messages that not processed yet. also we can prevent executing a message with specific MessgaeIdmultiple times. after executing our inbox message for example with calling our subscribed event handlers we send a ACK to the queue when they succeeded. (Inbox part of the system is in progress, I will cover this part soon as possible)

Also here I used RabbitMQ as my Message Broker for my async communication between the microservices with using eventually consistency mechanism, for now I used Spring AMQP for handling event driven communications. beside of using eventually consistency through event driven architecture we have a synchronous calls with using REST when we need immediate consistency.

I used Spring Cloud Gateway which is an API Gateway built on top of the Spring as reverse proxy (we can use envoy, traefik, nginx, ...), in front of our services. We can also have multiple Api Gateways for reaching BFF pattern. For example one Gateway for mobile apps, One Gateway for web apps and etc. With using api Gateway our internal microservices are transparent and user can not access them directly and all requests will serve through this Gateway. Also we can use gateway for load balancing, authentication and authorization, caching ,...

Application Structure

In this project I used vertical slice architecture or Restructuring to a Vertical Slice Architecture also I used feature folder structure in this project.

  • We treat each request as a distinct use case or slice, encapsulating and grouping all concerns from front-end to back.
  • When We adding or changing a feature in an application in n-tire architecture, we are typically touching many different "layers" in an application. we are changing the user interface, adding fields to models, modifying validation, and so on. Instead of coupling across a layer, we couple vertically along a slice and each change affects only one slice.
  • We Minimize coupling between slices, and maximize coupling in a slice.
  • With this approach, each of our vertical slices can decide for itself how to best fulfill the request. New features only add code, we're not changing shared code and worrying about side effects. For implementing vertical slice architecture using cqrs pattern is a good match.

Also here I used CQRS for decompose my features to very small parts that makes our application:

  • maximize performance, scalability and simplicity.
  • adding new feature to this mechanism is very easy without any breaking change in other part of our codes. New features only add code, we're not changing shared code and worrying about side effects.
  • easy to maintain and any changes only affect on one command or query (or a slice) and avoid any breaking changes on other parts
  • it gives us better separation of concerns and cross cutting concern (with help of MediatR behavior pipelines) in our code instead of a big service class for doing a lot of things.

With using CQRS, our code will be more aligned with SOLID principles, especially with:

  • Single Responsibility rule - because logic responsible for a given operation is enclosed in its own type.
  • Open-Closed rule - because to add new operation you don’t need to edit any of the existing types, instead you need to add a new file with a new type representing that operation.

Here instead of some Technical Splitting for example a folder or layer for our services, controllers and data models which increase dependencies between our technical splitting and also jump between layers or folders, We cut each business functionality into some vertical slices, and inner each of these slices we have Technical Folders Structure specific to that feature (command, handlers, infrastructure, repository, controllers, data models, ...).

Usually, when we work on a given functionality we need some technical things for example:

  • API endpoint (Controller)
  • Request Input (Dto)
  • Request Output (Dto)
  • Some class to handle Request, For example Command and Command Handler or Query and Query Handler
  • Data Model

Now we could all of these things beside each other and it decrease jumping and dependencies between some layers or folders.

Keeping such a split works great with CQRS. It segregates our operations and slices the application code vertically instead of horizontally. In Our CQRS pattern each command/query handler is a separate slice. This is where you can reduce coupling between layers. Each handler can be a separated code unit, even copy/pasted. Thanks to that, we can tune down the specific method to not follow general conventions (e.g. use custom SQL query or even different storage). In a traditional layered architecture, when we change the core generic mechanism in one layer, it can impact all methods.

High Level Structure

TODO

How to Run

Run the docker-compose.infrastructure.yaml file in the root of project, for running required infrastructures to run microservices with bellow command:

docker-compose -f ./deployments/docker-compose/docker-compose.infrastructure.yaml up -d

Now we can run our microservices with using mvn and make command and some predefined commands in our Makefile:

Build & Install Services

For running services firstly we should build and install building-blocks and shared service modules:

make build-building-blocks     
make install-building-blocks     

make build-shared   
make install-shared   

Then we should build and install other microservices:

make build-catalogs 
make install-catalogs  

make build-users    
make install-users

make build-customers   
make install-customers

make build-api-gateway   
make install-api-gateway

make build-orders   
make install-orders

Run Services

make run-catalogs

make run-users

make run-customers

make run-api-gateway

Test Services

make test-catalogs

make test-customers

make test-users

make test-all

Run Migrations

flyway-migrate-catalogs

flyway-migrate-users

flyway-migrate-customers

Other Useful Commands

Some useful commands with using make and mvn:

# Clean all microservices
make clean-all

# check style rules with spotless
make check-spotless

# apply style rules with spotless
make apply-spotless

Contribution

The application is in development status. You are feel free to submit pull request or create the issue.

Project References

License

The project is under MIT license.

About

☕A practical and imaginary food delivery microservices, built with java spring, domain-driven design, cqrs, vertical slice architecture, event-driven architecture, and the latest technologies.

Topics

Resources

License

Stars

Watchers

Forks

Languages