This content originally appeared on Level Up Coding - Medium and was authored by Sean Gu
DDD or Non-DDD? — A Comparative Solution Modeling
A simple yet practical enterprise solution modeling approach that empowers DDD (Domain-Driven Design)

DDD (Domain-Driven Design) is a widely discussed topic, yet it can be hard to understand in many cases. This article aims to provide a clear picture through a simple modeling approach.
For easy understanding, I use a straightforward use case: order placement, comparing its DDD and conventional non-DDD architectural designs, assuming you have a basic knowledge of DDD terms (see Appendix I for a brief definition list).
Below, I will present two minimal yet complete architectural designs tailored to the specific case scenario that aligns with its core implementation codebase. As this article focuses on architectural design, I have included the code in Appendix II for your reference.
For an effective DDD, solution designers or developers need to have a clear, collaborative model to reach consensus with business domain experts.
Table of Contents
DDD Version of “Order Placement”
Conventional Version of “Order Placement”
Key Differences between DDD and Conventional Non-DDD
What if in a Change Scenario
Architectural Decision on DDD
Appendix I: A Brief Definition of the Key DDD Terms
Appendix II: Comparative Implementation of DDD vs. Non-DDD
DDD Version of “Order Placement”
As shown in the following figure, the DDD version of order placement utilizes a bounded context with a rich domain model containing an aggregate root (which acts as a service component/entity for single-unit data changes), a domain service, and a domain event (communicated via an event bus).
It also utilizes a clean architecture containing a UI, an application service (which acts as a thin-layer process orchestrator), the domain, and the implicit infrastructure (which includes the repository store and event bus).
The Place Order Service application service communicates with the Inventory and Payment bounded contexts via anti-corruption (ACL) calls (implemented by the architectural guardrail).

Note that at an architectural design discussion, the object-level elements are not shown in the model. For example, the order aggregate contains value objects (Order, Customer, LineItems, Money, OrderStatus, Product, and OrderPlaced), which can be easily mapped in a further design level model view (see my article “DDD Simplified: Three Governing Views” for its UML design view). Also, the architectural model view only represents the chosen elements. The customer’s bounded context, for example, is excluded from the architectural model for a more focused view of this comparative case.
Conventional Version of “Order Placement”
The following illustrates the conventional version of order placement, which features anemic entities and a transactional service layer, along with data access objects (DAOs).

Obviously, it’s much easier to make things work using a non-DDD approach in the short run.
Key Differences between DDD and Conventional Non-DDD
You may notice the connection flows between elements in the above architectural model diagrams. The DDD diagram [Figure 1] commonly represents information flows in a more business-oriented mindset, in contrast to the technical mindset as seen in the conventional version of the diagram [Figure 2], which tends to use control/protocol flows.
Here are the key differences between DDD and conventional non-DDD mindsets.
Application Impact
- DDD: Behavior-driven by business requirements
- Conventional: Data-driven by database schema and design
Business Logic Location and Ownership
- DDD: Inside the Order Aggregate (behavior-rich)
- Conventional: Spread across Order Service and DAOs
Data Access
- DDD: Order Repository interface only (infrastructure hidden)
- Conventional: DAOs that are directly injected and called
Language Specification
- DDD: Ubiquitous language (Order.create, Money)
- Conventional: Technical language (OrderEntity, BigDecimal)
What if in a Change Scenario
Now comes the key comparison between DDD and non-DDD when the solution is subject to constant changes.
DDD shines when requirements change frequently in a complex business environment. Let’s say, there is a new requirement for Add Loyalty Points: “Whenever an order is successfully placed, award loyalty points to the customer.” What will happen to the changes of design and implementation in both DDD and non-DDD cases?
DDD Change
For the DDD design, the only change is in the domain layer.

- Add a minuscule class LoyaltyPolicy, based on the requirement rule or invariant (DDD’s term).
- Raise a new Royalty Event
Conventional Non-DDD Change
For the conventional design, there is a cross-layer change, as no one owns the requirement rule.

- Service layer: Add call loyaltyClient.award(custId, points) to OrderService.
- DAO layer: Either extend OrderDao to return total so the service can compute points, or add a new LoyaltyDao to persist points.
- Database schema: Create a new table/column for loyalty points.
- Integration test: Update mocks for loyaltyClient.
So, the conventional non-DDD approach distributes the same change across DAOs, services, and external clients.
Architectural Decision on DDD
DDD requires a steep learning curve and disciplined modeling skills, with a higher initial cost, such as the need for more boilerplates. It is likely overkill for CRUD-heavy or low-complexity domains, and its misuse (such as incorrect boundaries) will yield accidental complexity. Therefore,
Use DDD when
- Deep, shared language between business & development (ubiquitous language)
- Clear boundaries (bounded contexts) that reduce coupling and blast radius
- Rich domain model that captures rules & invariants explicitly for fewer defects
- For an enterprise that encourages strategic patterns (context mapping, aggregate design) and scale, evolving cleanly with complex business logic over time
Use conventional non-DDD when
- Simple to grasp and implement, and a quick start for small apps
- Less ceremony and formality, fewer layers for faster delivery
- When business rules are few and relatively stable
As you can see, DDD is not a good fit when changing requirement rules necessitates module/code modifications outside of the domain layer. Practically, a service-based architecture or a tactical DDD is often used (also called hybrid DDD or DDD lite). This maintains the simplicity of conventional non-DDD for approximately 70–90% of the code, but applies DDD patterns only where complexity justifies them.
Summary
This article demonstrates a DDD architectural modeling for simplicity and clarity. Modeling is an agile and iterative process for identifying unclear points and aiming for enhanced architectural thinking or a shared understanding.
AI can assist coding dramatically, help analyze and provide alternatives, but the context-specific architectural modeling and its architectural decisions are up to humans to weigh over and decide.
I hope the visual and comparative approach facilitates your understanding of DDD’s essence. If you are interested in DDD solution modeling using common-sense architectural elements, you can read my article on the topic. If you want to have a more complete DDD architectural design model, you can read my article on the topic of DDD’s three views (including DDD dependency view and design view).
Thank you for your time. I’d love to hear from you if you have any comments or requests related to enterprise solution architecture (ESA) modeling.
Reference
- Mastering Enterprise Solution Modeling/Gu, Sean. APRESS, 2024
- Domain-Driven Design/Evans, Eric. Addison-Wesley, 2015
- Domain-Driven Design Quickly/Avram, Abe. C4Media, 2006
Appendix I: A Brief Definition of the Key DDD Terms
The comparative solution modeling includes the following core DDD concepts:
- Bounded Context — a boundary that defines the scope within which a specific model and implementation apply.
- Application Service — responsible for orchestrating the workflow and interactions between the domain and other layers.
- Domain Model — representing the core business logic and rules of an application system.
- Domain Service — a way to encapsulate domain logic that doesn’t naturally fit into an entity or value object.
- Aggregate — a group of entities and value objects as a single unit for data changes.
- Value Object — small, immutable object
- Repository — managing data access for domain objects, abstracting persistence details.
- Variant: a rule or constraint in a business domain that must always hold.
- Domain Event — capturing the state change in the domain model.
- DDD Layered Architecture — including interface, application, domain (the core layer), and infrastructure layers.
Appendix II: Comparative Implementation of DDD vs. Non-DDD
The coding below contains common logic for model correlation of the order placement case implemented in both the DDD and conventional non-DDD approaches.
DDD — Java Implementation
Domain Service (including order aggregate, value objects (product, customer, money, etc.), and domain events)
// Value Objects
// =============
public record ProductId(String value) {}
public record CustomerId(String value) {}
public record Money(BigDecimal amount) {}
// Entity
// ======
@Aggregate
public class Order {
@Id private OrderId id;
private CustomerId custId;
private Map<ProductId,Integer> LineItems;
private Money total;
private OrderStatus status;
public static Order create(CustomerId custId,
Map<ProductId,Integer> LineItems,
PricingService pricing) {
var total = pricing.calculate(LineItems);
var order = new Order(OrderId.newId(), custId, LineItems, total, OrderStatus.PENDING);
DomainEvents.raise(new OrderPlaced(order.id, custId, total));
return order;}
}
// Domain Event
// ============
public record OrderPlaced(OrderId id, CustomerId custId, Money total) implements DomainEvent {}
Application Service (for use-case orchestration)
// Service
// =======
@RequiredArgsConstructor
public class PlaceOrderService {
private final OrderRepository objOrdersRepos;
private final CustomerRepository objCustomersRepos;
private final PricingService objPricing;
private final objInventoryGateway objInventoryGateway;
private final PaymentGateway objPaymentGateway;
@Transactional
public void place(PlaceOrderCommand cmd) {
var customer = objCustomersRepos.find(cmd.custId())
.orElseThrow(() -> new CustomerNotFound(cmd.custId()));
// ACL calls
objInventoryGateway.reserve(cmd.LineItems());
objPaymentGateway.preAuthorize(customer, cmd.total());
var order = Order.create(customer.id(), cmd.LineItems(), objPricing);
objOrdersRepos.save(order);}
}
Infrastructure Service (just a simplified representation)
// Repository
// ==========
class JPAOrderRepos implements OrderRepository {}
@EventListener
class InventoryEvent {
public void on(OrderPlaced event) {
inventory.reserve(event.LineItems());}
}
Non-DDD — Java Implementation
Entities (an anemic design)
// Entity
// ======
public class OrderEntity {
@Id private Long id;
private Long custId;
@ElementCollection private Map<Long,Integer> LineItems;
private BigDecimal total;
private String status;}
// Entity
// ======
public class CustomerEntity {
@Id private Long id;
private BigDecimal creditLimit;}
Service (transaction script)
// Service
// =======
@Transactional
public class OrderService {
@Autowired private OrderDao objOrderDao;
@Autowired private CustomerDao objCustomerDao;
@Autowired private InventoryDao objInventoryDao;
@Autowired private PaymentClient objPaymentClient;
public void placeOrder(Long custId, Map<Long,Integer> prodQty) {
var varCust = objCustomerDao.find(custId);
if (varCust == null) throw new IllegalArgumentException("customer");
var total = calculateTotal(prodQty);
if (customer.getCreditLimit().compareTo(total) < 0)
throw new IllegalStateException("credit");
objInventoryDao.reserve(prodQty); // inline SQL
objPaymentClient.preAuth(custId, total); // remote call
var order = new OrderEntity();
order.setCustomerId(custId);
order.setLineItems(prodQty);
order.setTotal(total);
order.setStatus("PENDING");
objOrderDao.save(order); }
private BigDecimal calculateTotal(Map<Long,Integer> LineItems) { }
}
DDD Change
Domain Event (a new Royalty Event)
// New Domain Policy
// =================
public final class LoyaltyPolicy {
public static int pointsFor(Money total) {
return total.amount().intValue();
}
}
// Update PlaceOrderService
// ========================
@Transactional
public void place(PlaceOrderCommand cmd) {
orders.save(order);
int pts = LoyaltyPolicy.pointsFor(order.total());
DomainEvents.raise(new LoyaltyPointsAwarded(order.custId(), pts));
}
DDD or Non-DDD? — A Comparative Solution Modeling was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Sean Gu

Sean Gu | Sciencx (2025-09-03T15:02:51+00:00) DDD or Non-DDD? — A Comparative Solution Modeling. Retrieved from https://www.scien.cx/2025/09/03/ddd-or-non-ddd-a-comparative-solution-modeling-2/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.