Showing posts with label DDD architecture. Show all posts
Showing posts with label DDD architecture. Show all posts

Tuesday, June 30, 2026

Domain-Driven Design (DDD) Explained with a Real-World Banking Project (.NET)

 Domain-Driven Design (DDD) is a software design approach introduced by Eric Evans. It focuses on modeling software around the business domain rather than technical implementation details.

Simply put:

DDD = Understand the Business First, Then Write the Code

Instead of asking:

"Which database table should I create?"

DDD asks:

"How does the business actually work?"


Why DDD?

Imagine you are building an Internet Banking System.

Traditional Approach:

UI
 ↓
Business Logic
 ↓
Database

Problems:

  • Business logic scattered everywhere

  • Difficult to maintain

  • Hard to test

  • Database drives design

  • Tight coupling

DDD Approach:

Business Domain
      ↓
Application Layer
      ↓
Infrastructure
      ↓
Database

Business rules become the center of the application.


Real-World Project

Let's build:

Online Banking System

Features

  • Customer Registration

  • Account Creation

  • Deposit Money

  • Withdraw Money

  • Transfer Money

  • Loan Request

  • Transaction History

Large organizations using similar concepts:

  • HDFC

  • ICICI

  • SBI

  • PayPal

  • Stripe


Step 1: Understand the Business

Business experts explain:

Customer owns Accounts.

Each Account has Balance.

Customer can transfer money.

Transfer must:

  • Check sender balance

  • Deduct sender balance

  • Credit receiver

  • Record transaction

  • Send notification

Notice:

No one mentioned SQL Server.

No one mentioned Entity Framework.

Because DDD starts from business.


Step 2: Identify Domains

Banking System

├── Customer
├── Accounts
├── Loans
├── Payments
├── Notifications
├── Authentication

Each becomes its own domain.


Step 3: Identify Bounded Contexts

A bounded context defines where a particular model is valid.

Example:

+----------------------+
| Customer Context     |
+----------------------+

Customer
Address
Profile
KYC

Another context:

+----------------------+
| Banking Context      |
+----------------------+

Account
Balance
Transaction
Transfer

Loan Context:

Loan

EMI

Interest

Approval

Authentication Context:

Login

JWT

Refresh Token

Permissions

Each context is independent.


Complete Architecture

                    Banking System

      +---------------------------------------+

         Customer Context

         Banking Context

         Loan Context

         Payment Context

         Notification Context

         Identity Context

      +---------------------------------------+

Each can become its own Microservice.


DDD Layers

Presentation

↓

Application

↓

Domain

↓

Infrastructure

Let's understand each.


1. Presentation Layer

Contains

  • ASP.NET Core API

  • MVC

  • Angular

  • React

Example:

POST

/api/account/transfer

No business logic.

Only:

Receive request

Call Application Layer


2. Application Layer

Responsible for

  • Use cases

  • Coordination

  • Transactions

Example

TransferMoneyCommand

TransferMoneyCommand

↓

TransferMoneyHandler

↓

Domain

↓

Repository

↓

Save

No business rules here.

Only orchestration.


3. Domain Layer (Heart of DDD)

Contains:

  • Entities

  • Value Objects

  • Aggregates

  • Domain Events

  • Interfaces

  • Business Rules

Everything important lives here.


Example Entity

Account

Id

AccountNumber

Balance

CustomerId

Methods

Deposit()

Withdraw()

Transfer()

Notice

Not

Balance = Balance - amount

from Controller.

Instead

Account.Withdraw(amount)

Business logic belongs inside entity.


Example

public class Account
{
    public decimal Balance { get; private set; }

    public void Deposit(decimal amount)
    {
        if(amount <=0)
            throw new Exception("Invalid amount");

        Balance += amount;
    }

    public void Withdraw(decimal amount)
    {
        if(amount > Balance)
            throw new Exception("Insufficient funds");

        Balance -= amount;
    }
}

Business rule:

Cannot withdraw more than balance.

Lives inside entity.


Value Objects

Example:

Address

Street

City

State

ZipCode

Identity doesn't matter.

Two identical addresses are equal.

Example:

Address A

=

Address B

Value Objects are immutable.

Another example:

Money

100 INR

instead of

decimal

Aggregate

Aggregate groups related objects.

Example:

Customer

↓

Accounts

↓

Transactions

Customer is Aggregate Root.

Only root is accessed directly.


Example

Customer

↓

Savings Account

↓

Current Account

↓

Transactions

Don't modify child objects directly.

Go through Aggregate Root.


Repository Pattern

Instead of

DbContext.Accounts.First()

Use

IAccountRepository

Example

public interface IAccountRepository
{
    Task<Account> GetById(Guid id);

    Task Save(Account account);
}

Infrastructure implements it.


Infrastructure Layer

Contains

  • SQL Server

  • EF Core

  • RabbitMQ

  • Azure Service Bus

  • Redis

  • Email

  • Azure Storage

Nothing business-related.


Example

AccountRepository

↓

Entity Framework

↓

SQL Server

Domain Events

Suppose money transferred.

Need to

  • Send Email

  • SMS

  • Push Notification

  • Audit Log

Don't call everything directly.

Instead raise event.

MoneyTransferredEvent

Event

Notification Service

Email Service

Audit Service

Analytics

Loose coupling.


Example

public class MoneyTransferredEvent
{
    public Guid FromAccount { get; set; }

    public Guid ToAccount { get; set; }

    public decimal Amount { get; set; }
}

CQRS with DDD

Separate

Commands

Deposit

Withdraw

Transfer

Queries

Get Balance

Get Transactions

Get Customer

Never mix.


Architecture

API

↓

Command

↓

Handler

↓

Domain

↓

Repository

↓

Database

Queries

API

↓

Read Database

↓

DTO

Very scalable.


Folder Structure

BankingSystem

│

├── Banking.API

├── Banking.Application

│

├── Banking.Domain

│

├── Banking.Infrastructure

│

├── Banking.Shared

│

└── Banking.Tests

Inside Domain

Domain

│

├── Entities

├── ValueObjects

├── Events

├── Repositories

├── Aggregates

├── Exceptions

├── Enums

Application

Application

│

├── Commands

├── Queries

├── DTOs

├── Interfaces

├── Handlers

Infrastructure

Infrastructure

│

├── Persistence

├── Repositories

├── Messaging

├── Azure

├── Email

├── Redis

API

Controllers

Program.cs

Middleware

Swagger

Filters

Transfer Money Flow

Angular

↓

Transfer API

↓

Transfer Command

↓

Command Handler

↓

Account Aggregate

↓

Withdraw()

↓

Deposit()

↓

Raise Event

↓

Repository

↓

SQL Server

↓

Notification

DDD + Microservices

Customer Service

↓

Account Service

↓

Loan Service

↓

Payment Service

↓

Notification Service

Each service owns its own database and domain model.


Advantages

  • Business-focused design

  • Clear separation of concerns

  • Easier maintenance

  • Easier testing

  • Highly scalable

  • Fits microservices well

  • Encourages rich domain models

  • Better communication with domain experts

  • Reduces duplicated business logic


Challenges

  • Steeper learning curve

  • More upfront design effort

  • Can be overkill for small CRUD applications

  • Requires close collaboration with business experts

  • More classes and abstractions than simple layered architectures


Common Interview Questions

1. What is Domain-Driven Design?

A software design approach that models software around the business domain, placing business rules in the domain model rather than spreading them across controllers or data access code.

2. What is a Bounded Context?

A clearly defined boundary within which a specific domain model and vocabulary are valid. Different bounded contexts can have different models for the same real-world concept.

3. What is an Entity?

An object defined by its identity that can change over time, such as a Customer or Account.

4. What is a Value Object?

An immutable object defined by its values rather than identity, such as Money or Address.

5. What is an Aggregate?

A cluster of related entities and value objects treated as a single consistency boundary, with an Aggregate Root controlling access.

6. What is an Aggregate Root?

The main entity of an aggregate through which all changes to the aggregate are made, ensuring business invariants are maintained.

7. What is a Repository?

An abstraction for loading and saving aggregates without exposing persistence details.

8. What are Domain Events?

Events raised by the domain to signal that something important has happened (for example, MoneyTransferredEvent), allowing other parts of the system to react without tight coupling.

9. How does DDD work with CQRS?

Commands change state through the domain model, while queries read optimized data models. This separation improves scalability and keeps business logic focused.

10. When should you use DDD?

DDD is most valuable for complex business domains—such as banking, insurance, healthcare, logistics, and e-commerce—where business rules are rich and evolve over time. For simple CRUD applications with minimal business logic, a traditional layered architecture is often sufficient.


Summary

In the banking example, DDD helps you model concepts like Accounts, Customers, Money Transfers, and Transactions the way the business understands them. Business rules such as "an account cannot be overdrawn" live inside the Account aggregate, application services coordinate use cases, repositories abstract persistence, and domain events notify other services of important changes. This leads to software that is easier to evolve, test, and scale as business requirements grow.

Saturday, October 25, 2025

🧩 What is a Bounded Context? – Explained with Examples

🧩 What is a Bounded Context?

A Bounded Context is a Domain-Driven Design (DDD) concept introduced by Eric Evans.
It defines clear boundaries within which a specific model (data + logic + rules) applies consistently.

In simple words:

It’s the logical boundary where a particular part of your application’s domain has its own language, rules, and data model.


🏗️ 1. Bounded Context in a Monolithic Architecture

In a Monolithic application:

  • The entire system is a single deployable unit (one codebase, one database).

  • But within it, you can still have multiple bounded contexts (logical boundaries).

These contexts are implemented as modules, namespaces, or layers in the same codebase.

🧠 Example

Let’s consider an E-Commerce Monolith:

ModuleDescriptionExample Functionality
Order ContextManages ordersPlace Order, Cancel Order
Inventory ContextManages stockUpdate Quantity, Reserve Item
Payment ContextManages transactionsProcess Payment, Refund Payment

Each module:

  • Has its own data model (Order, Payment, etc.)

  • Uses internal APIs or function calls between modules.

  • Lives inside one shared database, but might use separate schemas or tables.

🔍 Communication Example

OrderService.placeOrder() -> InventoryService.reserveStock() -> PaymentService.processPayment()
  • All services are in-process calls.

  • Boundaries are logical, not physical.

✅ Advantages

  • Simple deployment (single unit)

  • Easier transaction management

  • Easy to share code between contexts

❌ Disadvantages

  • Tight coupling — changes in one module may impact others

  • Hard to scale specific contexts independently

  • Difficult to evolve or rewrite individual modules


☁️ 2. Bounded Context in a Microservices Architecture

In Microservices, the Bounded Context becomes the natural boundary of a service.

Each Microservice = One Bounded Context.

Each has:

  • Its own database

  • Its own domain model

  • Its own deployment lifecycle

🧠 Example

Rewriting the same E-Commerce system in microservices:

MicroserviceBounded ContextDatabase
Order ServiceOrder ContextOrdersDB
Inventory ServiceInventory ContextInventoryDB
Payment ServicePayment ContextPaymentsDB

🔍 Communication Example

Communication happens across services using:

  • REST APIs

  • gRPC

  • Message queues (RabbitMQ, Kafka, etc.)

Order Service --> (API Call) --> Inventory Service --> (API Call) --> Payment Service

Each microservice enforces its own data and business rules without sharing its internal data structures.

✅ Advantages

  • Clear separation of concerns

  • Independent scalability and deployment

  • Easier to use the best technology per service

  • Resilience — one failure doesn’t crash the entire system

❌ Disadvantages

  • Distributed system complexity (network, latency, transactions)

  • Data consistency challenges (need eventual consistency)

  • More operational overhead (deployment, monitoring, versioning)


🧩 Summary Table

FeatureMonolithicMicroservices
Boundary TypeLogicalPhysical
Deployment UnitSingle applicationMultiple services
CommunicationIn-processNetwork (HTTP/gRPC/Events)
DatabaseShared or partitioned schemaIndependent per service
CouplingHighLow
ScalingEntire appPer service
TransactionSimple (single DB)Complex (distributed)

💡 Real-World Example

Amazon (Monolithic → Microservices)
Initially had a large monolithic app where “Orders”, “Inventory”, “Payments” were just modules.
Now each one is a separate bounded context microservice, communicating via event-driven architecture (Kafka) and APIs, enabling independent scaling and deployment.


🏁 Summary Definition

  • In a Monolithic system: a Bounded Context is a module or logical boundary inside one codebase.

  • In a Microservice system: a Bounded Context is a physically separated service, with its own code, data, and deployment.


----------------------------------------------------------------------------------------------------------------


Introduction

In modern software architecture, especially in Domain-Driven Design (DDD) and Microservices, the term “Bounded Context” plays a critical role. It defines clear boundaries around a specific part of the business domain — ensuring that your system remains modular, scalable, and easy to maintain.

Let’s explore what a Bounded Context means, how it works, and why it’s essential in both Monolithic and Microservice architectures.


🧠 Definition of Bounded Context

A Bounded Context is a logical boundary within your application where a particular domain model applies consistently.
It helps ensure that terms, data, and behaviors inside that context have one and only one meaning.

In simple terms —

A bounded context defines where one model ends and another begins.

Each context has its own language, rules, and data models that don’t interfere with others.


📘 Example: E-Commerce System

Imagine an E-commerce application. It has multiple business domains such as:

  • Order Management

  • Inventory

  • Payments

  • Customer Support

Each of these can be treated as a Bounded Context:

  • In the Order Context, the term “Customer” might mean a buyer placing orders.

  • In the Support Context, “Customer” might represent someone who raises tickets or complaints.

Although both use the term “Customer”, they mean different things — hence they belong to different bounded contexts.


🏗️ Bounded Context in Monolithic Architecture

In a Monolithic Application, all code is deployed as a single unit.
However, you can still apply bounded context principles by organizing your project into modules or layers.

For example:

/EcommerceApp /Orders /Payments /Inventory /Support

Each folder acts as an internal bounded context — helping teams avoid confusion and maintain clarity even inside a monolith.

Benefits:

  • Better modularity

  • Easier debugging

  • Reduced model confusion


☁️ Bounded Context in Microservices Architecture

In Microservices, each bounded context becomes a separate service with its own:

  • Database

  • Domain Model

  • API Contracts

For example:

  • OrderService handles order placement and tracking.

  • PaymentService handles transactions and billing.

  • InventoryService tracks product availability.

Each microservice has a single, well-defined purpose and communicates via APIs — ensuring data integrity and autonomy.


🔄 Communication Between Bounded Contexts

Bounded contexts often interact using:

  1. API Calls (REST or gRPC)

  2. Domain Events

  3. Message Queues (RabbitMQ, Kafka)

This decoupling allows each context to evolve independently without breaking others.


⚙️ Advantages of Using Bounded Contexts

✅ Clear separation of responsibilities
✅ Improved maintainability and scalability
✅ Team autonomy (each team owns one context)
✅ Reduced coupling between modules
✅ Better alignment with business domains


⚖️ Monolithic vs Microservices – Bounded Context Comparison

FeatureMonolithicMicroservices
DeploymentSingle unitIndependent services
Model boundaryLogical (within same app)Physical (separate services)
DatabaseSharedIndependent
ScalabilityLimitedHighly scalable
Change impactAffects entire systemAffects only one service

💡 Best Practices

  • Identify business domains and subdomains before coding.

  • Define ubiquitous language per context.

  • Avoid shared databases between contexts.

  • Use context mapping to visualize dependencies.

  • Keep integration asynchronous where possible.


🧭 Conclusion

A Bounded Context acts as a clear boundary for your domain models — whether you’re building a monolithic or microservice system.
It’s one of the key principles of Domain-Driven Design, enabling teams to work independently while maintaining a coherent business logic.

By defining and respecting your bounded contexts, you’ll build systems that are clean, scalable, and aligned with business goals

Don't Copy

Protected by Copyscape Online Plagiarism Checker