OrderFlow is a portfolio project built to practice common backend engineering patterns in C# / .NET 8, with a focus on reliability and clean separation of responsibilities.
OrderFlow showcases common backend engineering patterns in C# and .NET 8. The system implements a simplified order management workflow with emphasis on data consistency, reliability, and observability.
Key Focus Areas:
- Clean architecture with explicit layer boundaries
- Transactional consistency using the Outbox pattern
- Reliable message processing with retry logic and dead-letter handling
- Request tracing and correlation
- Focused unit and integration tests for core reliability scenarios
Note: This project focuses on backend architecture and reliability. Swagger is used as the primary interface.
OrderFlow was built as a learning-focused portfolio project during my transition from C++ systems-style programming into enterprise .NET backend development.
The main goal is to practice patterns commonly found in real-world backend teams:
- transactional consistency (ACID mindset)
- reliable async processing (Outbox + worker)
- observability and debugging
- clean separation of responsibilities
v0.1.0 - Learning MVP
Features:
- Order and order item management
- SQL Server persistence with EF Core migrations
- Simplified Transactional Outbox pattern
- Background worker with retry logic
- Request correlation middleware
- Focused test coverage (persistence, outbox processing, retries, dead-letter, concurrency)
| Layer | Technology |
|---|---|
| Runtime | .NET 8 |
| Web Framework | ASP.NET Core (Minimal APIs) |
| ORM | Entity Framework Core |
| Database | SQL Server Express |
| Web Demo UI | React + Vite |
| Testing | xUnit |
OrderFlow follows Clean Architecture principles with clear separation of concerns:
src/
├── OrderFlow.Api/ # HTTP layer, endpoints, middleware, hosted services
├── OrderFlow.Application/ # Business logic, use cases, abstractions
├── OrderFlow.Domain/ # Domain models and business rules
└── OrderFlow.Infrastructure/ # Data access, EF Core context, repositories
tests/
└── OrderFlow.Tests/ # Unit and integration tests
OrderFlow.Api ──────────> OrderFlow.Application ──────> OrderFlow.Domain
│ ▲
│ │
└──> OrderFlow.Infrastructure ┘
Principles:
- Domain layer has no external dependencies
- Application layer depends only on Domain abstractions
- Infrastructure implements Application interfaces
- API layer orchestrates dependency injection and hosting
- Correlation ID Middleware: Automatically generates or propagates
X-Correlation-Idheaders - Structured Logging: Correlation IDs are included in log scope for distributed tracing
- Enables end-to-end request tracking across async operations
Ensures data consistency when performing database updates and publishing events:
- Atomic Write: Order state changes and outbox messages are saved in a single transaction
- Asynchronous Processing: Background worker polls and processes messages independently
- At-least-once delivery pattern: Messages are persisted before being processed, ensuring at-least-once delivery
Example Flow:
Order Confirmation Request
↓
Save Order Status + Outbox Message (single transaction)
↓
Background Worker Claims Message
↓
Process Event Handler
↓
Mark as Processed
The outbox processor implements robust message handling:
- Polling: Continuously checks for pending messages
- Atomic Claiming: Uses database-level locking to prevent duplicate processing across workers
- Retry Logic: Exponential backoff for transient failures
- Dead Letter Queue: Messages exceeding retry limit are marked as dead
- Handler Dispatch: Type-based routing to appropriate handlers
Read-only endpoints for operational monitoring:
- Query messages by status (pending, processed, dead)
- Retrieve individual message details
- Useful for debugging and operational visibility
| Method | Endpoint | Description |
|---|---|---|
POST |
/orders |
Create a new order |
GET |
/orders |
List all orders |
GET |
/orders/{id} |
Get order details |
POST |
/orders/{id}/items |
Add item to order |
POST |
/orders/{id}/confirm |
Confirm order (triggers outbox) |
POST |
/orders/{id}/cancel |
Cancel order |
| Method | Endpoint | Description |
|---|---|---|
GET |
/outbox?status={status} |
List messages by status |
GET |
/outbox/{id} |
Get message details |
- .NET 8 SDK
- SQL Server Express (or any SQL Server instance)
- (Optional) Node.js 18+ (for the demo web UI)
-
Clone the repository
git clone https://github.com/CarAraujo-Dev/OrderFlow.git cd OrderFlow -
Configure database connection
Update
appsettings.jsoninOrderFlow.Apiwith your SQL Server connection string.Example:
"ConnectionStrings": { "Default": "Server=localhost\\SQLEXPRESS;Database=OrderFlowDbV2;Trusted_Connection=True;TrustServerCertificate=True;" }
-
Apply migrations
dotnet ef database update \ --project src/OrderFlow.Infrastructure \ --startup-project src/OrderFlow.Api
This will create the database schema automatically if it does not exist.
-
Run the application
dotnet run --project src/OrderFlow.Api
-
Access Swagger UI
Navigate to
https://localhost:<port>/swagger
The web demo UI is optional and mainly exists as a lightweight interface for demo purposes.
-
Go to the web project folder:
cd src/OrderFlow.Web -
Install dependencies:
npm install
-
Start the dev server:
npm run dev
-
Open the app:
http://localhost:5173
✅ If the API runs on a different port, you may need to configure the API base URL in the web app.
Try this sequence to see the Outbox pattern in action:
# 1. Create an order
POST /orders
{
"customerName": "Carlos"
}
# 2. Add an item
POST /orders/1/items
{
"name": "Melamine Desk",
"quantity": 2,
"unitPrice": 29.99
}
# 3. Confirm the order (publishes outbox message)
POST /orders/1/confirm
# 4. Check pending messages
GET /outbox?status=pending
# 5. Wait ~1 second for background processing
# 6. Verify message was processed
GET /outbox?status=processedIntegration tests use a dedicated SQL Server database via a separate Testing connection string.
Location:
tests/appsettings.Testing.json
Example:
{
"ConnectionStrings": {
"Testing": "Server=localhost\\SQLEXPRESS;Database=OrderFlowDb_Test;Trusted_Connection=True;TrustServerCertificate=True"
}
}Notes:
Note: Test parallelization is disabled because some integration tests reset the testing database. Running them in parallel can cause conflicts and failures due to concurrent DB teardown/setup.
Test Coverage:
- Unit Tests: Business logic and domain rules (using EF Core InMemory provider)
- Integration Tests: Outbox processing, concurrency scenarios, database interactions
This project adheres to the following guidelines:
-
Explicit Boundaries: Each layer has a clear responsibility
Api: HTTP hosting and transport concernsApplication: Business workflows and use case orchestrationDomain: Pure business logic and entitiesInfrastructure: Database and external system integration
-
Incremental Evolution: Changes are made in small, tested increments
-
Pragmatic Abstractions: Interfaces exist where they provide value, not everywhere
-
Tests as Documentation: Integration tests run against SQL Server (using EF Core migrations) and are executed sequentially to avoid conflicts during database reset.
Potential enhancements for future versions:
- Worker status metrics endpoint
- Docker Compose setup for easier local development
- Improved logging and observability
- Rate limiting
- Idempotency for confirm endpoint
- CI/CD pipeline basics
This is a learning project, but feedback and suggestions are welcome! Feel free to open issues for discussion or suggestions on how to improve the architecture.
This project is licensed under the MIT License - see the LICENSE file for details.
Carlos Araujo
GitHub: https://github.com/CarAraujo-Dev