Open-source project
mehdihadeli/food-delivery-microservices avatar
mehdihadeli/food-delivery-microservices

Food Delivery Microservices: a .NET reference implementation with several architectures in one

🍔 A practical and cloud-native food delivery microservices, built with .Net Aspire, .Net 10, Wolverine, Domain-Driven Design, CQRS, Vertical Slice Architecture, Event-Driven Architecture, and the latest technologies.

1,014 stars194 forksC#MIT

At a glance

What is it?
This repository is a food delivery system built as a set of .NET services, using vertical slices, CQRS, domain driven design, event sourcing where it makes sense, and Wolverine over RabbitMQ for messaging, orchestrated with .NET Aspire. The author says plainly that it is not business oriented and exists to demonstrate techniques, and the same domain is also available as a modular monolith and as a Go implementation.
Who is it for?
Adopt this repository as a reference rather than as a starting point for a real product, because the author states that the focus is technical and that the domain is incidental, and because the same domain is offered as a modular monolith and in Go, which is the way to compare the architectures honestly. Do not copy it into production without reading the release notes, since the project is explicitly in progress and adds features over time.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 9 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A reference implementation, and the author says so

Food Delivery Microservices is described as a practical and cloud-native food delivery system built with .NET Aspire, .NET and a set of architecture approaches, and the very next paragraph in the README is the most important thing in the document.

The application is not business oriented, and the focus is mostly on the technical part. The stated goal is to implement a sample using different technologies, software architecture design and principles, and everything needed to create a microservices application.

That sentence reframes the whole repository. A food delivery domain with customers, catalogues, orders and payments is a way to have a domain that is familiar and rich enough to exercise every part of the architecture without inventing one. Nobody is going to use this for ordering pizza.

And the author goes further, publishing the same domain in other shapes so the architectures can be compared rather than merely admired. There is a modular monolith version in a separate repository, and a Go implementation of the microservices version. The framing is that you can choose the best fit architecture for your projects based on production needs.

There is also a smaller template for the simplest case, a vertical slice API template for .NET projects, which is the same ideas without the distributed system around them.

The project is MIT licensed and written in C#, with the last push to the repository on 2026-10-14 and the current release v1.5.0 from 2026-10-10. The README also carries a warning that the project is in progress and that new features are added over time, with the release notes linked as the place to see them.

The service boundaries and what each one uses

The features list is where the architecture becomes concrete, and the striking thing about it is that different services use different data strategies on purpose.

Domain driven design is used in most of the services, the Customers and Catalogs services named as examples. Event sourcing with EventStoreDB is used in the audit based services, Orders and Payment. The Identity Service is a data centric architecture based on CRUD instead. And the CQRS pattern on top of MediatR splits read models from write models.

So this is not a sample where every service follows the same recipe. The justification is legible from the service names. An identity service holds users and credentials, and there is no event history worth keeping for it, so relational CRUD is the honest choice. An orders and payment service has a compliance requirement, a dispute has to be reconstructable, so the state is stored as an append only event stream and the current state is a projection of it.

The transport is split too. Communication between independent services is asynchronous messaging with RabbitMQ and Wolverine, and sometimes synchronous communication for real time with REST and gRPC calls.

The read side is separate from the write side. Postgres is the relational write database, and MongoDB and ElasticSearch serve the read side, and Minimal APIs handle the request handling at the edge, with YARP acting as the reverse proxy in front of the services as an API gateway.

The features list also records what is not done. The entry for Helm, Kubernetes and Kustomize for deployment is unchecked, while the docker and docker-compose entry is checked, so the container story is complete and the Kubernetes story is planned.

Wolverine, the outbox pattern and the transaction boundary

Messaging is the part of the system that a reference implementation can most easily get wrong, and this one takes it seriously enough to have a whole section of the README about it.

Wolverine is the message bus and transactional messaging framework, used for the event driven architecture on top of RabbitMQ. The feature list specifies Wolverine durable messaging with PostgreSQL persistence for the outbox, the inbox and durable local processing in a transactional boundary with EF Core.

That is the mechanism, and it is the interesting design. Publishing a message and writing a database row are two different systems, and the failure modes are well known. Publishing first and crashing before the write loses the event. Writing first and crashing before the publish means the message is never sent. The outbox pattern solves it by writing the intent into the same transaction as the data change and letting a separate process pick it up and publish it, and an inbox does the same on the receiving side so that a message is only processed once even if it arrives twice.

The inbox here is also doing idempotency work. The feature list says the Wolverine durable inbox handles idempotency on the receiver side, with practical exactly-once processing semantics per message id. That is the part that matters in practice, because a broker that delivers at least once will deliver twice, and a consumer that is not idempotent will double-charge a customer.

The section on the transaction boundary is about where the database transaction ends and the message publish begins, which is exactly the question a distributed system has to answer explicitly rather than by accident.

Observability, testing and the documentation of events

Three supporting practices are implemented, and each of them is a common thing to leave out.

Observability runs through the OpenTelemetry Collector, which receives, processes and exports telemetry data to various backends, including Jaeger and Tempo for tracing, Loki and Kibana for logs, and Prometheus for metrics. The point of a collector in the middle is that the application does not have to know which backend anyone is looking at today, and that the exporters can change without touching the services.

Testing is layered. Unit tests use NSubstitute for mocking dependencies. Integration tests and end to end tests run on top of testcontainers-dotnet, which starts the real dependencies in Docker containers and tears them down afterwards. There is also a validation pipeline behaviour on top of MediatR, with Fluent Validation in front of it, so that a request is validated before a handler sees it.

The third practice is unusual and worth calling out. EventCatalog is used to document the asynchronous messages, the domains and the services, and the live catalog is published. For a system where the contracts between services are messages, a catalog of those messages is the equivalent of an API description, and keeping it generated from the code rather than written in a document is what stops the two from drifting.

The plan table in the README is empty apart from its headers, which is an honest signal that the roadmap lives in the release notes rather than in the readme.

The build, the toolchain and the quality gates

The Makefile is the most concrete evidence of how the project is maintained, and it is organised around the idea that a formatting or analyser regression should fail a build.

The format check runs the CSharpier formatter in check mode, and there are separate targets for style rules and for analyser rules, both run through dotnet format with the verify-no-changes flag, so the check fails rather than rewriting the files.

bash
check-format:
	dotnet csharpier check $(PROJECT_PATH)

check-style:
	dotnet format style $(PROJECT_PATH) --verify-no-changes --severity error --verbosity diagnostic

check-analyzers:
	dotnet format analyzers $(PROJECT_PATH) --verify-no-changes --severity error --verbosity diagnostic

The severity levels are the detail that makes this usable. The check targets run at error level, while the fix targets exist at error, warning and information level, so the strictness of the codebase is a dial rather than a constant.

The prepare target restores the.NET tools and installs the Husky.Net hooks. There are also targets to run the project, to run a single project, to run the Aspire application, and to upgrade packages through dotnet outdated.

Around the Makefile the repository carries the usual modern.NET configuration: a solution file in the new XML solution format, central package management through Directory.Packages.props, a shared Directory.Build.props, a global.json pinning the SDK, a mise configuration for the other tools, a StyleCop configuration, a CSharpier configuration, and a secret scanning configuration for gitleaks.

Getting it running, and where the friction is

The getting started section is a checklist, and it is worth reading as a description of the setup cost.

There is a Codespaces badge, which is the fastest route and tells you something about the shape of the project, since the whole system needs Postgres, MongoDB, ElasticSearch, RabbitMQ and an OpenTelemetry collector before anything interesting happens.

The prerequisites are followed by a clone with submodules, and the submodule step is a named item, the Squirrel BuildingBlocks submodule, which is a set of shared building blocks the services reference. Then a development certificate, the conventional commit convention, formatting and analyser setup.

Running is either through docker-compose, which the features list confirms is supported, or through Kubernetes, following the plain Kubernetes instructions in the README.

The friction is real and the README does not hide it. This is a large system with many moving parts, and the value it offers is the set of techniques rather than the food delivery. A team that wants the patterns in a smaller project has the vertical slice API template, and a team that wants to compare against a single deployable has the modular monolith version, and the Go version is there for the same comparison across languages.

The.NET Aspire piece deserves one more word, because it is what makes the local story bearable. Aspire handles the orchestration of the cloud native application, so the dependencies are described rather than hand started, and the developer experience is the reason a project with five datastores and a broker is something a newcomer can run at all.

Editorial conclusion

Adopt this repository as a reference rather than as a starting point for a real product, because the author states that the focus is technical and that the domain is incidental, and because the same domain is offered as a modular monolith and in Go, which is the way to compare the architectures honestly. Do not copy it into production without reading the release notes, since the project is explicitly in progress and adds features over time. Before you run it, read the getting started section, because the build depends on submodules, a development certificate and a specific toolchain pinned in global.json.

Frequently asked questions

What is the Food Delivery Microservices project?

It is a .NET microservices sample for a food delivery domain, using .NET Aspire, vertical slice architecture, CQRS with MediatR, domain driven design, event driven architecture with RabbitMQ and Wolverine, and minimal APIs with YARP as the gateway. The author states that it is not business oriented and that the focus is on the technical patterns.

Does every service in the sample use the same data strategy?

No. Event sourcing with EventStoreDB is used in the audit based services such as Orders and Payment, the Identity Service uses a data centric CRUD approach, and CQRS with MediatR splits read models from write models, with Postgres for writes and MongoDB and ElasticSearch for reads.

How does the sample guarantee that messages are not lost or processed twice?

It uses Wolverine durable messaging with PostgreSQL persistence for the outbox, the inbox and durable local processing inside the transaction boundary with EF Core. The durable inbox handles idempotency on the receiver side, giving practical exactly-once processing semantics per message id.

How do I run the project locally?

The fastest route is a GitHub Codespace, since the system needs Postgres, MongoDB, ElasticSearch, RabbitMQ and an OpenTelemetry collector. Otherwise clone with submodules, install the development certificate, and run it with docker-compose or with .NET Aspire, whose aspire-run Makefile target starts the orchestrated application.

Official sources

  1. Issues
  2. License: MIT
  3. mehdihadeli/food-delivery-microservices on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mehdihadeli-food-delivery-microservices.svg)](https://hysenlabs.com/projects/mehdihadeli-food-delivery-microservices)

Was this useful?