How I Built a CQRS Approval Flow with MediatR, Carter, FluentValidation, and SQLite

Leader posted 3 min read

The tech stack I used and why (updated, with Repository and Unit of Work)

This project is small, but I wanted it to feel like a real API you can extend. The repository is here:

https://github.com/stevsharp/ChainOfResponsibilityWithMediatR

Carter for endpoints

I used Carter because I like minimal APIs, but I still want structure. Carter lets you group endpoints by feature (modules) without big controllers, so the API layer stays clean. (Microsoft Learn)

CQRS with MediatR

CQRS is the separation of:

  • Commands (write, change state)
  • Queries (read, return data)

With MediatR, the endpoint just does mediator.Send(...) and the logic lives in a handler. This keeps endpoints thin and makes features easy to follow.

ICommand and IQuery marker interfaces

I added ICommand and IQuery as simple marker interfaces on top of MediatR requests. This is not a “framework”, it is only a clean way to tag what is a write and what is a read.

The reason is pipeline behaviors. With these markers, I can run transactions only for commands, and avoid wrapping queries.

MediatR pipeline behaviors

Pipeline behaviors are like middleware around every handler (IPipelineBehavior<TRequest, TResponse>). (

In this project I use:

  • LoggingBehavior, so I see execution time and request names
  • ValidationBehavior, so invalid requests never reach business logic
  • TransactionBehavior, so commands run inside a transaction

This keeps handlers focused, no repeated boilerplate.

FluentValidation

I used FluentValidation because it keeps validation clean and testable. It also supports assembly scanning registration, so validators are discovered automatically.

Repository pattern (why it exists here)

The Repository is used as a boundary between the application and persistence. In simple words, the application talks to an interface like IPurchaseOrderRepository, and Infrastructure decides how the data is stored (EF Core, SQLite, later maybe something else).

Conceptually, a repository “looks like” an in-memory collection for your domain objects, but it hides the database mapping details.

This is the classic definition from Fowler. (martinfowler.com)

In this project, repositories help me keep handlers free from EF Core code, so handlers read more like business use cases.

Unit of Work (and how it connects to DbContext)

A Unit of Work groups changes and commits them together. Fowler describes it as tracking everything you do in a business transaction and coordinating writing changes to the database. (martinfowler.com)

Important detail: in EF Core, DbContext is already designed to be used as a unit of work. Microsoft docs even explain that a DbContext instance is typically used for a single unit of work, then you call SaveChanges and dispose it. (Microsoft Learn)

So why still have IUnitOfWork in the Application layer?

Because it is not only about “needing” it, it is about controlling dependencies. Sometimes you want:

  • handlers to never call EF directly
  • one SaveChangesAsync per command (enforced in one place)
  • the possibility to swap persistence later without touching application code

So IUnitOfWork is an Application abstraction, Infrastructure implements it by calling DbContext.SaveChangesAsync. (Microsoft Learn)

EF Core + SQLite

EF Core is the persistence technology, SQLite is the lightweight database so the project runs locally with minimal setup. For a demo project, it is perfect.

Also, EF Core saving is straightforward: you change tracked entities, then call SaveChanges or SaveChangesAsync to persist. (Microsoft Learn)

Swagger (Swashbuckle)

Swagger is here so you can test endpoints fast without Postman. It gives visibility to request and response contracts, which is very useful in demos and early development.

Domain patterns, Chain of Responsibility and Specification

The business part is the approval flow. I used Chain of Responsibility to avoid one big if/else if method, and I used Specification style rules so conditions stay readable and composable. This makes it easy to add new approval levels without rewriting old logic. (Coder Legion)


References

  • GitHub repository:
https://github.com/stevsharp/ChainOfResponsibilityWithMediatR
  • Original article on CoderLegion:
https://coderlegion.com/8906/chain-of-responsibility-inside-cqrs-with-mediatr-a-small-practical-example
  • How to use Carter in a CQRS API, advantages and alternatives :
https://coderlegion.com/9342/carter-in-a-cqrs-api-advantages-and-alternatives
  • MediatR pipeline behaviors (IPipelineBehavior):
  • FluentValidation DI scanning:
  • Repository pattern (Fowler): (martinfowler.com)
  • Unit of Work pattern (Fowler): (martinfowler.com)
  • EF Core: DbContext as unit of work + SaveChanges lifecycle: (Microsoft Learn)

1 Comment

1 vote
0

More Posts

How I Built a React Portfolio in 7 Days That Landed ₹1.2L in Freelance Work

Dharanidharan - Feb 9

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelskiverified - Mar 19

Carter in a CQRS API, advantages and alternatives.

Spyros - Jan 7

Chain of Responsibility inside CQRS (with MediatR), a small practical example

Spyros - Dec 25, 2025

Everyone says DeepSeek is cheaper, but I got tired of guessing the exact math. So I built a calculat

abarth23 - Apr 27
chevron_left

Related Jobs

View all jobs →

Commenters (This Week)

4 comments
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!