Techcookies

Hexagonal Architecture and DDD

Architecture, Design Patterns | Mon Apr 27 2026 | 87 min read

PART B — HEXAGONAL ARCHITECTURE


PART B — Hexagonal Architecture

  1. What is Hexagonal Architecture?
  2. Ports — The Contracts
  3. Adapters — The Implementations
  4. The Dependency Rule
  5. Project Structure

PART C — DDD + Hexagonal Together

  1. How DDD and Hexagonal Relate
  2. Mapping DDD Concepts to Hexagonal Layers
  3. Complete End-to-End Example
  4. DDD + Hexagonal with Microservices
  5. Anti-Patterns to Avoid

PART D — Interview Questions

  1. 50 Interview Questions with Answers

16. What is Hexagonal Architecture?

The Plain English Explanation

Hexagonal Architecture (also called Ports and Adapters Architecture) was created by Alistair Cockburn in 2005. It solves one fundamental problem:

"How do we build software where the business logic is completely independent of databases, frameworks, UIs, and external APIs — so we can swap any of them without touching the business code?"

The answer: Put all business logic inside a protected core. Define contracts (Ports) for everything going in or out. Build Adapters to connect real technology to those contracts.

The Problem It Solves

WITHOUT HEXAGONAL — The Big Ball of Mud:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  @Service
  public class OrderService {
      @Autowired EntityManager em;           // JPA leaking into business logic!
      @Autowired HttpServletRequest request; // HTTP in business logic!
      @Autowired KafkaTemplate kafka;        // Kafka in business logic!

      public void placeOrder() {
          String userId = request.getHeader("X-User-Id"); // Reading HTTP headers!
          Order order = em.find(Order.class, id);         // JPA query in service!
          kafka.send("orders", order);                     // Kafka in business logic!
          em.persist(order);                               // JPA persist in service!
      }
  }

  Problems:
  ❌ Cannot test without starting Spring, database, and Kafka
  ❌ Cannot switch from MySQL to MongoDB without rewriting service
  ❌ Cannot switch from REST to gRPC without rewriting service
  ❌ Business logic buried under framework noise
  ❌ Every change risks breaking everything

WITH HEXAGONAL — Clean Separation:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  public class PlaceOrderService {         // Pure Java — zero framework deps
      private final OrderRepository repo; // Interface — not JPA!
      private final EventPublisher pub;   // Interface — not Kafka!

      public PlaceOrderResult execute(PlaceOrderCommand cmd) {
          Order order = Order.place(...);  // Pure domain logic
          repo.save(order);               // Calls interface — repo handles JPA
          pub.publish(order.events());    // Calls interface — pub handles Kafka
          return result;
      }
  }

  Benefits:
  ✅ Test service with in-memory fake repository — zero infrastructure
  ✅ Switch MySQL → MongoDB by writing a new adapter
  ✅ Switch REST → gRPC by writing a new adapter
  ✅ Business logic is clean, readable, focused
  ✅ Each part changes independently

The Hexagon Explained

The shape is a metaphor. A hexagon has multiple sides — each represents a different way to connect to the outside world (REST, gRPC, Kafka, CLI, tests). The number of sides doesn't matter. What matters is:

The core is protected on ALL sides. Nothing external leaks in.

Full Hexagonal Architecture Diagram

┌──────────────────────────────────────────────────────────────────────────┐
│                        HEXAGONAL ARCHITECTURE                            │
│                                                                          │
│  DRIVING SIDE                              DRIVEN SIDE                   │
│  (They call YOUR app)                      (YOUR app calls them)         │
│                                                                          │
│  ┌─────────────┐                                ┌──────────────────────┐│
│  │  REST API   │──▶[REST Adapter]─────────┐     │ [JPA Adapter]        ││
│  └─────────────┘                          │     │  MySQL Database      ││
│                                           │     └──────────────────────┘│
│  ┌─────────────┐                          │     ┌──────────────────────┐│
│  │  GraphQL    │──▶[GQL Adapter]──────────┤  ┌─▶│ [Kafka Adapter]      ││
│  └─────────────┘                          │  │  │  Kafka Cluster       ││
│                                           │  │  └──────────────────────┘│
│  ┌─────────────┐   ┌──────────────────┐  │  │  ┌──────────────────────┐│
│  │  CLI Tool   │──▶│  DRIVING PORT    │  │  │  │ [Razorpay Adapter]   ││
│  └─────────────┘   │  (interface)     │──┤  │  │  Payment Gateway     ││
│                    └────────┬─────────┘  │  │  └──────────────────────┘│
│  ┌─────────────┐            │            │  │  ┌──────────────────────┐│
│  │  Kafka Msg  │──▶[Kafka   │            │  │  │ [Twilio Adapter]     ││
│  │  Consumer   │   Adapter] │            │  │  │  SMS Gateway         ││
│  └─────────────┘            │            │  │  └──────────────────────┘│
│                    ┌────────▼────────────▼──┴─────────────────────┐    │
│                    │                                               │    │
│                    │         APPLICATION CORE (THE HEXAGON)        │    │
│                    │                                               │    │
│                    │  ┌─────────────────────────────────────────┐ │    │
│                    │  │           DOMAIN LAYER                  │ │    │
│                    │  │  Order, Customer, DeliveryFeePolicy,    │ │    │
│                    │  │  OrderPlacedEvent, Money, Address       │ │    │
│                    │  └─────────────────────────────────────────┘ │    │
│                    │  ┌─────────────────────────────────────────┐ │    │
│                    │  │         APPLICATION LAYER               │ │    │
│                    │  │  PlaceOrderService, CancelOrderService  │ │    │
│                    │  │  (implements Driving Ports)             │ │    │
│                    │  └─────────────────────────────────────────┘ │    │
│                    │                                               │    │
│                    │  ┌─────────────────────────────────────────┐ │    │
│                    │  │      DRIVEN PORTS (interfaces)          │ │    │
│                    │  │  OrderRepository, PaymentGateway,       │ │    │
│                    │  │  EventPublisher, NotificationSender     │ │    │
│                    │  └─────────────────────────────────────────┘ │    │
│                    └───────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────────────────┘

17. Ports — The Contracts

What is a Port?

A Port is simply a Java interface. Nothing more. It defines a contract for how the application interacts with the outside world. There are two types:

┌─────────────────────────────────────────────────────────────────────┐
│                         TWO TYPES OF PORTS                          │
├────────────────────────────────┬────────────────────────────────────┤
│      DRIVING PORTS             │          DRIVEN PORTS              │
│      (Inbound / Primary)       │          (Outbound / Secondary)    │
├────────────────────────────────┼────────────────────────────────────┤
│                                │                                    │
│  "How the OUTSIDE WORLD        │  "What YOUR APPLICATION            │
│   can talk to YOUR app"        │   needs from the outside world"    │
│                                │                                    │
│  Defined BY: Application Core  │  Defined BY: Application Core      │
│  Implemented BY: App Services  │  Implemented BY: Adapters          │
│  Called BY: Driving Adapters   │  Called BY: App Services           │
│                                │                                    │
│  Examples:                     │  Examples:                         │
│  PlaceOrderUseCase             │  OrderRepository                   │
│  CancelOrderUseCase            │  PaymentGateway                    │
│  GetOrderUseCase               │  NotificationSender                │
│  TrackDeliveryUseCase          │  DomainEventPublisher              │
│                                │  DeliveryPartnerAssigner           │
└────────────────────────────────┴────────────────────────────────────┘

Driving Ports — Code

java
// ═══════════════════════════════════════════════════════════
// application/port/in/PlaceOrderUseCase.java
// ═══════════════════════════════════════════════════════════

/**
 * DRIVING PORT — defines how the outside world can place an order.
 * Implemented by PlaceOrderService (in the application layer).
 * Called by REST adapter, CLI adapter, tests, etc.
 */
public interface PlaceOrderUseCase {

    PlaceOrderResult execute(PlaceOrderCommand command);

    // Input command — what the caller must provide
    record PlaceOrderCommand(
        String customerId,
        String restaurantId,
        String flatNo,
        String street,
        String city,
        String state,
        String pinCode,
        List<OrderItemData> items
    ) {
        public record OrderItemData(
            String menuItemId,
            String name,
            double unitPrice,
            int quantity
        ) {}
    }

    // Output result — what the caller receives
    record PlaceOrderResult(
        String orderId,
        double subtotal,
        double deliveryFee,
        double grandTotal,
        String estimatedDelivery
    ) {}
}


// ═══════════════════════════════════════════════════════════
// application/port/in/CancelOrderUseCase.java
// ═══════════════════════════════════════════════════════════
public interface CancelOrderUseCase {

    void execute(CancelOrderCommand command);

    record CancelOrderCommand(
        String orderId,
        String customerId,
        String reason
    ) {}
}


// ═══════════════════════════════════════════════════════════
// application/port/in/GetOrderUseCase.java
// ═══════════════════════════════════════════════════════════
public interface GetOrderUseCase {

    OrderSummary execute(GetOrderQuery query);

    record GetOrderQuery(String orderId, String requestingCustomerId) {}

    record OrderSummary(
        String orderId,
        String status,
        double totalAmount,
        String restaurantName,
        List<String> itemDescriptions
    ) {}
}

Driven Ports — Code

java
// ═══════════════════════════════════════════════════════════
// application/port/out/OrderRepository.java
// ═══════════════════════════════════════════════════════════

/**
 * DRIVEN PORT — contract for order persistence.
 * Defined by the Application Core (domain language).
 * Implemented by infrastructure adapters (JPA, MongoDB, etc.)
 */
public interface OrderRepository {
    void           save(Order order);
    void           delete(OrderId orderId);
    Optional<Order> findById(OrderId orderId);
    List<Order>    findByCustomerId(CustomerId customerId);
    List<Order>    findByStatus(OrderStatus status);
    boolean        existsById(OrderId orderId);
}


// ═══════════════════════════════════════════════════════════
// application/port/out/PaymentGateway.java
// ═══════════════════════════════════════════════════════════
public interface PaymentGateway {
    PaymentResult authorise(Order order, PaymentDetails details);
    RefundResult  refund(PaymentReference ref, Money amount);
}


// ═══════════════════════════════════════════════════════════
// application/port/out/DomainEventPublisher.java
// ═══════════════════════════════════════════════════════════
public interface DomainEventPublisher {
    void publish(List<DomainEvent> events);
}


// ═══════════════════════════════════════════════════════════
// application/port/out/NotificationSender.java
// ═══════════════════════════════════════════════════════════
public interface NotificationSender {
    void sendOrderConfirmation(CustomerId customerId, Order order);
    void sendOrderCancellation(CustomerId customerId, String reason);
    void sendDeliveryUpdate(CustomerId customerId, String message);
}

18. Adapters — The Implementations

What is an Adapter?

An Adapter is the glue code that connects a real technology to a Port. It translates between the world outside and your application core. Adapters are always in the Infrastructure layer.

ADAPTER RESPONSIBILITY TABLE:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  Adapter                  Port It Connects          Direction
  ─────────────────────    ──────────────────────    ──────────
  OrderController (REST)   PlaceOrderUseCase         Driving
  OrderGQLResolver         GetOrderUseCase           Driving
  OrderCLI                 CancelOrderUseCase        Driving
  OrderKafkaConsumer       CancelOrderUseCase        Driving
  JpaOrderRepository       OrderRepository           Driven
  KafkaEventPublisher      DomainEventPublisher      Driven
  RazorpayAdapter          PaymentGateway            Driven
  TwilioNotificationSend   NotificationSender        Driven
  InMemoryOrderRepo        OrderRepository           Driven (tests!)

Driving Adapter — REST Controller

java
// infrastructure/adapter/in/web/OrderController.java
// DRIVING ADAPTER: Translates HTTP → Use Case Command

@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {

    // ← Depends on the PORT (interface), NOT the implementation
    private final PlaceOrderUseCase  placeOrderUseCase;
    private final CancelOrderUseCase cancelOrderUseCase;
    private final GetOrderUseCase    getOrderUseCase;

    // ── POST /api/v1/orders ───────────────────────────────
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public PlaceOrderHttpResponse placeOrder(
            @RequestBody  PlaceOrderHttpRequest  request,
            @AuthenticationPrincipal UserDetails user) {

        // Translate: HTTP request → PlaceOrderCommand
        PlaceOrderCommand command = new PlaceOrderCommand(
            user.getUsername(),
            request.restaurantId(),
            request.address().flatNo(),
            request.address().street(),
            request.address().city(),
            request.address().state(),
            request.address().pinCode(),
            request.items().stream()
                .map(i -> new OrderItemData(i.menuItemId(), i.name(), i.unitPrice(), i.qty()))
                .toList()
        );

        // Call the PORT (use case)
        PlaceOrderResult result = placeOrderUseCase.execute(command);

        // Translate: PlaceOrderResult → HTTP response
        return new PlaceOrderHttpResponse(
            result.orderId(),
            result.grandTotal(),
            result.deliveryFee(),
            result.estimatedDelivery()
        );
    }

    // ── DELETE /api/v1/orders/{orderId} ───────────────────
    @DeleteMapping("/{orderId}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void cancelOrder(
            @PathVariable String orderId,
            @RequestParam  String reason,
            @AuthenticationPrincipal UserDetails user) {
        cancelOrderUseCase.execute(
            new CancelOrderCommand(orderId, user.getUsername(), reason)
        );
    }

    // ── GET /api/v1/orders/{orderId} ──────────────────────
    @GetMapping("/{orderId}")
    public OrderDetailHttpResponse getOrder(
            @PathVariable String orderId,
            @AuthenticationPrincipal UserDetails user) {
        OrderSummary summary = getOrderUseCase.execute(
            new GetOrderQuery(orderId, user.getUsername())
        );
        return new OrderDetailHttpResponse(
            summary.orderId(), summary.status(), summary.totalAmount()
        );
    }

    // ── HTTP-only DTOs ─────────────────────────────────────
    // These only exist for HTTP transport — never leak into domain
    record PlaceOrderHttpRequest(
        String restaurantId,
        AddressDto address,
        List<ItemDto> items
    ) {}
    record AddressDto(String flatNo, String street, String city, String state, String pinCode) {}
    record ItemDto(String menuItemId, String name, double unitPrice, int qty) {}
    record PlaceOrderHttpResponse(String orderId, double total, double fee, String eta) {}
    record OrderDetailHttpResponse(String orderId, String status, double total) {}
}

Driving Adapter — Kafka Consumer

java
// infrastructure/adapter/in/messaging/OrderKafkaConsumer.java
// DRIVING ADAPTER: Translates Kafka message → Use Case Command

@Component
public class OrderKafkaConsumer {

    private final CancelOrderUseCase cancelOrderUseCase;

    @KafkaListener(topics = "support.order-cancellation-requests", groupId = "order-service")
    public void handleCancellationRequest(OrderCancellationMessage message) {
        // Translate Kafka message → Cancel command
        cancelOrderUseCase.execute(
            new CancelOrderCommand(
                message.orderId(),
                message.customerId(),
                "Cancelled by support: " + message.supportNote()
            )
        );
    }

    record OrderCancellationMessage(String orderId, String customerId, String supportNote) {}
}

Driven Adapter — JPA Repository

java
// infrastructure/adapter/out/persistence/JpaOrderRepositoryAdapter.java
// DRIVEN ADAPTER: Translates OrderRepository → JPA/MySQL

@Repository
public class JpaOrderRepositoryAdapter implements OrderRepository {

    private final SpringDataOrderJpaRepo jpaRepo;
    private final OrderPersistenceMapper mapper;

    @Override
    public void save(Order order) {
        OrderJpaEntity entity = mapper.toJpaEntity(order);
        jpaRepo.save(entity);
    }

    @Override
    public Optional<Order> findById(OrderId orderId) {
        return jpaRepo.findById(orderId.toString())
                      .map(mapper::toDomainObject);
    }

    @Override
    public List<Order> findByCustomerId(CustomerId customerId) {
        return jpaRepo.findByCustomerId(customerId.toString())
                      .stream()
                      .map(mapper::toDomainObject)
                      .toList();
    }

    @Override
    public List<Order> findByStatus(OrderStatus status) {
        return jpaRepo.findByStatus(status.name())
                      .stream()
                      .map(mapper::toDomainObject)
                      .toList();
    }

    @Override
    public boolean existsById(OrderId orderId) {
        return jpaRepo.existsById(orderId.toString());
    }

    @Override
    public void delete(OrderId orderId) {
        jpaRepo.deleteById(orderId.toString());
    }
}

// The JPA entity — completely separate from the domain model
@Entity
@Table(name = "orders")
class OrderJpaEntity {
    @Id
    @Column(name = "order_id")
    private String orderId;

    @Column(name = "customer_id")
    private String customerId;

    @Column(name = "restaurant_id")
    private String restaurantId;

    @Column(name = "status")
    private String status;

    @Column(name = "total_amount")
    private BigDecimal totalAmount;

    @Column(name = "currency")
    private String currency;

    @Column(name = "placed_at")
    private LocalDateTime placedAt;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "order")
    private List<OrderItemJpaEntity> items = new ArrayList<>();

    // Standard getters and setters for JPA
}

Driven Adapter — Kafka Event Publisher

java
// infrastructure/adapter/out/messaging/KafkaDomainEventPublisher.java
// DRIVEN ADAPTER: Translates DomainEventPublisher → Kafka

@Component
public class KafkaDomainEventPublisher implements DomainEventPublisher {

    private final KafkaTemplate<String, Object> kafkaTemplate;

    @Override
    public void publish(List<DomainEvent> events) {
        for (DomainEvent event : events) {
            String topic = topicFor(event);
            kafkaTemplate.send(topic, event.getEventId(), event);
        }
    }

    private String topicFor(DomainEvent event) {
        return switch (event.getEventType()) {
            case "ORDER_PLACED"     -> "order.placed";
            case "ORDER_CONFIRMED"  -> "order.confirmed";
            case "ORDER_CANCELLED"  -> "order.cancelled";
            case "ORDER_DISPATCHED" -> "order.dispatched";
            case "ORDER_DELIVERED"  -> "order.delivered";
            default -> throw new IllegalArgumentException("Unknown event: " + event.getEventType());
        };
    }
}

Driven Adapter — In-Memory (For Testing!)

java
// This is the POWER of Hexagonal Architecture
// You can swap real JPA for an in-memory adapter for TESTS
// WITHOUT changing any business logic code

// infrastructure/adapter/out/persistence/InMemoryOrderRepository.java
// Used in unit tests — no database needed!

public class InMemoryOrderRepository implements OrderRepository {

    private final Map<String, Order> store = new HashMap<>();

    @Override
    public void save(Order order) {
        store.put(order.getId().toString(), order);
    }

    @Override
    public Optional<Order> findById(OrderId orderId) {
        return Optional.ofNullable(store.get(orderId.toString()));
    }

    @Override
    public List<Order> findByCustomerId(CustomerId customerId) {
        return store.values().stream()
            .filter(o -> o.getCustomerId().equals(customerId))
            .toList();
    }

    @Override
    public List<Order> findByStatus(OrderStatus status) {
        return store.values().stream()
            .filter(o -> o.getStatus() == status)
            .toList();
    }

    @Override
    public boolean existsById(OrderId orderId) {
        return store.containsKey(orderId.toString());
    }

    @Override
    public void delete(OrderId orderId) {
        store.remove(orderId.toString());
    }

    // Test helper
    public int size() { return store.size(); }
}

// Now your unit test needs ZERO infrastructure:
class PlaceOrderServiceTest {

    private PlaceOrderService service;
    private InMemoryOrderRepository orderRepo;

    @BeforeEach
    void setUp() {
        orderRepo = new InMemoryOrderRepository();
        service = new PlaceOrderService(
            orderRepo,
            new InMemoryCustomerRepository(),
            new InMemoryRestaurantRepository(),
            new DeliveryFeePolicy(),
            new NoOpEventPublisher()  // Does nothing in tests
        );
    }

    @Test
    void should_place_order_successfully() {
        PlaceOrderResult result = service.execute(validCommand());
        assertThat(result.orderId()).isNotBlank();
        assertThat(orderRepo.size()).isEqualTo(1);
    }
}

19. The Dependency Rule

The Most Critical Rule

╔══════════════════════════════════════════════════════════════╗
║              THE DEPENDENCY RULE                             ║
║                                                              ║
║  Dependencies point INWARD ONLY.                             ║
║  The Core NEVER depends on the outside.                      ║
║  The outside depends on the Core through Ports.              ║
║                                                              ║
║  Adapters → Ports → Application Core → Domain                ║
║                                                              ║
║  ✅ OrderController imports PlaceOrderUseCase (port)          ║
║  ✅ PlaceOrderService imports OrderRepository (port)          ║
║  ✅ JpaOrderRepo imports Order (domain object)                ║
║                                                              ║
║  ❌ Order imports OrderController (VIOLATION!)               ║
║  ❌ PlaceOrderService imports KafkaTemplate (VIOLATION!)     ║
║  ❌ Domain class imports @Entity JPA annotation (VIOLATION!) ║
╚══════════════════════════════════════════════════════════════╝

Visualising the Dependency Rule

                 ALLOWED ✅            FORBIDDEN ❌
                 ──────────            ────────────

  OrderController ──depends on──▶ PlaceOrderUseCase (Port)
  PlaceOrderUseCase ──depends on──▶ PlaceOrderService (App Core)
  PlaceOrderService ──depends on──▶ OrderRepository (Port)
  JpaOrderRepo ──depends on──▶ Order (Domain)

  Never: Order ──▶ JpaOrderRepo       (domain knows infrastructure)
  Never: PlaceOrderService ──▶ KafkaTemplate  (app knows messaging impl)
  Never: Order ──▶ @Entity annotation (domain knows JPA)

  RULE: If you see "import org.springframework" or "import javax.persistence"
        inside your domain/ folder = ARCHITECTURE VIOLATION

20. Project Structure

order-service/
│
├── src/main/java/com/fooddelivery/order/
│   │
│   ├── domain/                                 ← ⭐ Heart (zero framework deps)
│   │   ├── model/
│   │   │   ├── order/
│   │   │   │   ├── Order.java                  Aggregate Root
│   │   │   │   ├── OrderItem.java              Entity
│   │   │   │   ├── OrderId.java                Value Object
│   │   │   │   ├── OrderStatus.java            Enum
│   │   │   │   └── events/
│   │   │   │       ├── OrderPlacedEvent.java
│   │   │   │       ├── OrderConfirmedEvent.java
│   │   │   │       ├── OrderCancelledEvent.java
│   │   │   │       └── OrderDeliveredEvent.java
│   │   │   └── shared/
│   │   │       ├── Money.java                  Value Object
│   │   │       ├── Address.java                Value Object
│   │   │       ├── Quantity.java               Value Object
│   │   │       └── DomainEvent.java            Interface
│   │   └── service/
│   │       └── DeliveryFeePolicy.java          Domain Service
│   │
│   ├── application/                            ← Orchestration layer
│   │   ├── port/
│   │   │   ├── in/                             ← DRIVING PORTS
│   │   │   │   ├── PlaceOrderUseCase.java
│   │   │   │   ├── CancelOrderUseCase.java
│   │   │   │   └── GetOrderUseCase.java
│   │   │   └── out/                            ← DRIVEN PORTS
│   │   │       ├── OrderRepository.java
│   │   │       ├── CustomerRepository.java
│   │   │       ├── RestaurantRepository.java
│   │   │       ├── PaymentGateway.java
│   │   │       ├── DomainEventPublisher.java
│   │   │       └── NotificationSender.java
│   │   └── service/                            ← USE CASE IMPLEMENTATIONS
│   │       ├── PlaceOrderService.java          implements PlaceOrderUseCase
│   │       ├── CancelOrderService.java
│   │       └── GetOrderService.java
│   │
│   └── infrastructure/                         ← All adapters & technical config
│       ├── adapter/
│       │   ├── in/                             ← DRIVING ADAPTERS
│       │   │   ├── web/
│       │   │   │   └── OrderController.java
│       │   │   ├── graphql/
│       │   │   │   └── OrderGQLResolver.java
│       │   │   └── messaging/
│       │   │       └── OrderKafkaConsumer.java
│       │   └── out/                            ← DRIVEN ADAPTERS
│       │       ├── persistence/
│       │       │   ├── JpaOrderRepositoryAdapter.java
│       │       │   ├── OrderJpaEntity.java
│       │       │   └── OrderPersistenceMapper.java
│       │       ├── messaging/
│       │       │   └── KafkaDomainEventPublisher.java
│       │       ├── payment/
│       │       │   └── RazorpayPaymentGatewayAdapter.java
│       │       └── notification/
│       │           └── TwilioNotificationAdapter.java
│       └── config/
│           ├── BeanConfig.java
│           └── KafkaConfig.java
│
└── src/test/java/com/fooddelivery/order/
    ├── domain/
    │   └── OrderTest.java                      ← Unit tests (zero infra)
    ├── application/
    │   └── PlaceOrderServiceTest.java          ← Uses InMemory adapters
    └── infrastructure/
        └── web/
            └── OrderControllerIntegrationTest.java

PART C — DDD + HEXAGONAL TOGETHER


21. How DDD and Hexagonal Relate

Two Complementary Ideas

┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│   DDD asks:      "How do we MODEL the business correctly?"          │
│   HEXAGONAL asks: "How do we PROTECT the model from technology?"    │
│                                                                     │
│   DDD gives you:     Entities, Aggregates, Value Objects,           │
│                      Domain Events, Repositories (concepts)         │
│                                                                     │
│   HEXAGONAL gives you: The structure to keep DDD concepts           │
│                         PURE and ISOLATED from frameworks            │
│                                                                     │
│   Together: Your business logic is correctly modelled (DDD)         │
│             AND completely protected from technology (Hexagonal)    │
└─────────────────────────────────────────────────────────────────────┘

Analogy — The Kingdom

┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│  DDD = The LAWS, GOVERNANCE, and CULTURE of a kingdom.              │
│        It defines how the kingdom works, what the roles are,        │
│        how decisions are made, what the currency is.                │
│                                                                     │
│  HEXAGONAL = The CASTLE WALLS, MOAT, and DRAWBRIDGE.                │
│        They protect the kingdom from outside invasion.               │
│        All visitors must enter through the gates (Ports).           │
│        Guards (Adapters) check and translate.                       │
│                                                                     │
│  Without DDD good laws:   The kingdom makes poor decisions.         │
│  Without Hexagonal walls: Enemies (databases, frameworks)           │
│                            invade and pollute the kingdom.          │
│                                                                     │
│  TOGETHER: A well-governed, well-protected kingdom.                 │
└─────────────────────────────────────────────────────────────────────┘

22. Mapping DDD Concepts to Hexagonal Layers

┌─────────────────────────────────────────────────────────────────────┐
│              DDD CONCEPT → HEXAGONAL LAYER MAPPING                  │
├──────────────────────────┬──────────────────────────────────────────┤
│       DDD CONCEPT        │        HEXAGONAL LOCATION                │
├──────────────────────────┼──────────────────────────────────────────┤
│  Entity                  │  domain/model/  (Hexagon core)           │
│  Value Object            │  domain/model/  (Hexagon core)           │
│  Aggregate Root          │  domain/model/  (Hexagon core)           │
│  Domain Event            │  domain/model/events/ (Hexagon core)     │
│  Domain Service          │  domain/service/ (Hexagon core)          │
│  Repository (interface)  │  application/port/out/ (Driven Port)     │
│  Repository (impl)       │  infrastructure/adapter/out/ (Driven Adapter)│
│  Application Service     │  application/service/ (implements Port)  │
│  Factory                 │  domain/service/ OR application/service/ │
│  Bounded Context         │  One complete Hexagonal module/service   │
│  Context Mapping         │  Adapters at the boundaries              │
│  Anti-Corruption Layer   │  Driven Adapter (translates to domain)   │
│  Use Case                │  Driving Port (interface in app/port/in) │
│  REST Controller         │  Driving Adapter (infrastructure/in/web) │
│  Kafka Publisher         │  Driven Adapter (infrastructure/out/msg) │
└──────────────────────────┴──────────────────────────────────────────┘

Visual Mapping

┌──────────────────────────────────────────────────────────────────────┐
│                     COMBINED VIEW                                    │
│                                                                      │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │                    HEXAGON (Application Core)                 │  │
│  │                                                               │  │
│  │  ┌─────────────────────────────────────────────────────────┐ │  │
│  │  │                 DOMAIN LAYER (DDD)                       │ │  │
│  │  │                                                         │ │  │
│  │  │  Order (Aggregate)    ← Entity + Business Rules         │ │  │
│  │  │  OrderItem (Entity)   ← Owned by Aggregate              │ │  │
│  │  │  Money (ValueObject)  ← Immutable, self-validating      │ │  │
│  │  │  Address (ValueObject)← Immutable, self-validating      │ │  │
│  │  │  OrderPlacedEvent     ← Domain Event                    │ │  │
│  │  │  DeliveryFeePolicy    ← Domain Service                  │ │  │
│  │  └─────────────────────────────────────────────────────────┘ │  │
│  │                                                               │  │
│  │  ┌─────────────────────────────────────────────────────────┐ │  │
│  │  │              APPLICATION LAYER (DDD + Hexagonal)         │ │  │
│  │  │                                                         │ │  │
│  │  │  PlaceOrderUseCase    ← Driving Port (interface)        │ │  │
│  │  │  PlaceOrderService    ← Application Service (impl)      │ │  │
│  │  │  OrderRepository      ← Driven Port (interface)         │ │  │
│  │  │  PaymentGateway       ← Driven Port (interface)         │ │  │
│  │  └─────────────────────────────────────────────────────────┘ │  │
│  └───────────────────────────────────────────────────────────────┘  │
│                                                                      │
│  DRIVING ADAPTERS (infrastructure/in):                               │
│    OrderController → REST            (calls PlaceOrderUseCase port)  │
│    OrderGQLResolver → GraphQL        (calls GetOrderUseCase port)    │
│    OrderKafkaConsumer → Kafka        (calls CancelOrderUseCase port) │
│                                                                      │
│  DRIVEN ADAPTERS (infrastructure/out):                               │
│    JpaOrderRepo     → MySQL          (implements OrderRepository)    │
│    KafkaPublisher   → Kafka          (implements DomainEventPublisher│
│    RazorpayAdapter  → Razorpay API   (implements PaymentGateway)     │
│    TwilioAdapter    → Twilio SMS     (implements NotificationSender) │
└──────────────────────────────────────────────────────────────────────┘

23. Complete End-to-End Example

Scenario: Customer Places a Food Order

Let's trace a real request through EVERY layer of our DDD + Hexagonal architecture.

CUSTOMER ACTION: Priya opens the app and places an order:
  Restaurant: Domino's Pune
  Items: 1× Veggie Paradise Pizza ₹349, 2× Garlic Bread ₹99 each
  Address: B-5, Koregaon Park, Pune - 411001
  Payment: UPI

EXPECTED FLOW:
  1. HTTP POST /api/v1/orders
  2. REST Adapter translates → PlaceOrderCommand
  3. PlaceOrderService (Application Service) orchestrates
  4. Order domain object enforces business rules
  5. DeliveryFeePolicy calculates ₹0 (order > ₹299)
  6. JPA adapter persists to MySQL
  7. Kafka adapter publishes OrderPlacedEvent
  8. Payment service, Restaurant service, Notification service react
  9. Priya receives SMS: "Order placed! ₹547 charged. ETA 35 min"

Complete Code Flow

java
// ══════════════════════════════════════
// STEP 1: HTTP Request arrives
// ══════════════════════════════════════

// POST /api/v1/orders
// {
//   "restaurantId": "REST-DOMINOS-PUNE-042",
//   "address": { "flatNo": "B-5", "street": "Koregaon Park",
//                "city": "Pune", "state": "Maharashtra", "pinCode": "411001" },
//   "items": [
//     { "menuItemId": "ITEM-VEG-PARADISE", "name": "Veggie Paradise Pizza",
//       "unitPrice": 349.00, "quantity": 1 },
//     { "menuItemId": "ITEM-GARLIC-BREAD", "name": "Garlic Bread",
//       "unitPrice": 99.00, "quantity": 2 }
//   ]
// }

// ══════════════════════════════════════
// STEP 2: DRIVING ADAPTER — OrderController
// ══════════════════════════════════════
// Translates HTTP → PlaceOrderCommand
// Calls placeOrderUseCase.execute(command)
// Translates PlaceOrderResult → HTTP 201 response

// ══════════════════════════════════════
// STEP 3: APPLICATION SERVICE — PlaceOrderService
// ══════════════════════════════════════

PlaceOrderResult result = placeOrderService.execute(command);

// Inside PlaceOrderService.execute():
Customer customer = customerRepo.findById(CustomerId.of("CUST-PRIYA-99"))
    .orElseThrow();
// → customer: Priya, no Swiggy One subscription

Restaurant restaurant = restaurantRepo.findById(RestaurantId.of("REST-DOMINOS-PUNE-042"))
    .orElseThrow();
// → restaurant: Domino's Pune, isAcceptingOrders=true, isPremium=false

// ══════════════════════════════════════
// STEP 4: DOMAIN LOGIC — Order Aggregate
// ══════════════════════════════════════

Address deliveryAddress = new Address("B-5", "Koregaon Park", "Pune", "Maharashtra", "411001");
// → validated: pinCode "411001" matches \d{6} ✅

Order order = Order.place(
    CustomerId.of("CUST-PRIYA-99"),
    RestaurantId.of("REST-DOMINOS-PUNE-042"),
    itemRequests,
    deliveryAddress
);

// Inside Order.place():
// → OrderId generated: "ORD-7F3A2B1C"
// → OrderItem created: Veggie Paradise Pizza, ₹349 × 1 = ₹349
// → OrderItem created: Garlic Bread, ₹99 × 2 = ₹198
// → totalAmount = ₹349 + ₹198 = ₹547
// → status = PLACED
// → OrderPlacedEvent created and stored in domainEvents

// ══════════════════════════════════════
// STEP 5: DOMAIN SERVICE — DeliveryFeePolicy
// ══════════════════════════════════════

Money deliveryFee = deliveryFeePolicy.calculate(order, customer, restaurant, distance);

// Inside DeliveryFeePolicy.calculate():
// Rule 1: customer.hasSubscription(SWIGGY_ONE) → false
// Rule 2: order.getTotal() ₹547 > ₹299 → FREE DELIVERY!
// → deliveryFee = ₹0

// ══════════════════════════════════════
// STEP 6: DRIVEN ADAPTER — JPA Save
// ══════════════════════════════════════

orderRepository.save(order);
// → JpaOrderRepositoryAdapter converts Order → OrderJpaEntity
// → Spring Data JPA calls INSERT INTO orders (...)
// → OrderItemJpaEntity rows inserted

// ══════════════════════════════════════
// STEP 7: DRIVEN ADAPTER — Kafka Publish
// ══════════════════════════════════════

eventPublisher.publish(order.getDomainEvents());
// → KafkaDomainEventPublisher.publish([OrderPlacedEvent])
// → kafkaTemplate.send("order.placed", eventId, OrderPlacedEvent)

// ══════════════════════════════════════
// STEP 8: RETURN TO CONTROLLER
// ══════════════════════════════════════

return new PlaceOrderResult(
    "ORD-7F3A2B1C",
    547.00,    // subtotal
    0.00,      // deliveryFee (free!)
    547.00,    // grandTotal
    "35 mins"  // estimatedDelivery
);

// ══════════════════════════════════════
// STEP 9: DOWNSTREAM SERVICES REACT
// (Asynchronously via Kafka)
// ══════════════════════════════════════

// payment-service hears "order.placed":
//   → Authorise ₹547 from Priya's UPI
//   → PaymentAuthorisedEvent published

// restaurant-service hears "order.placed":
//   → Push notification to Domino's Pune tablet
//   → "New order! Veggie Paradise ×1, Garlic Bread ×2. Order by 2:45 PM."

// notification-service hears all events:
//   → SMS to Priya: "Order placed! ₹547 charged. ETA 35 mins."

// ══════════════════════════════════════
// FINAL HTTP RESPONSE to Priya's app:
// ══════════════════════════════════════
// HTTP 201 Created
// {
//   "orderId": "ORD-7F3A2B1C",
//   "total": 547.00,
//   "deliveryFee": 0.00,
//   "eta": "35 mins"
// }

24. DDD + Hexagonal with Microservices

One Bounded Context = One Microservice = One Hexagon

FOOD DELIVERY PLATFORM — MICROSERVICES + DDD + HEXAGONAL:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ┌──────────────────────────────────────────────────────────────┐
  │  order-service (port: 8081)                                  │
  │  Bounded Context: Order Management                            │
  │  DB: PostgreSQL (orders, order_items)                         │
  │  Kafka: Publishes order.* events                             │
  │  Hexagon: Full Ports & Adapters structure                    │
  └──────────────────────────────────────────────────────────────┘

  ┌──────────────────────────────────────────────────────────────┐
  │  restaurant-service (port: 8082)                             │
  │  Bounded Context: Restaurant Management                       │
  │  DB: MongoDB (restaurants, menus)                            │
  │  Kafka: Consumes order.placed, publishes restaurant.* events │
  │  Hexagon: Full Ports & Adapters structure                    │
  └──────────────────────────────────────────────────────────────┘

  ┌──────────────────────────────────────────────────────────────┐
  │  payment-service (port: 8083)                                │
  │  Bounded Context: Payment Processing                          │
  │  DB: PostgreSQL (payments, refunds)                          │
  │  External: Razorpay (via Anti-Corruption Layer adapter)      │
  │  Kafka: Consumes order.placed, publishes payment.* events    │
  └──────────────────────────────────────────────────────────────┘

  ┌──────────────────────────────────────────────────────────────┐
  │  delivery-service (port: 8084)                               │
  │  Bounded Context: Delivery Management                         │
  │  DB: PostgreSQL + Redis (assignments, tracking)              │
  │  External: Google Maps API (distance calculation)            │
  │  Kafka: Consumes payment.authorised, publishes delivery.*    │
  └──────────────────────────────────────────────────────────────┘

Communication Between Microservices

COMMUNICATION PATTERNS:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  SYNCHRONOUS (REST/gRPC):
  ─────────────────────────
  Use when: You need an immediate response.
  Example: Customer asks "What restaurants are near me?"
           → order-service calls restaurant-service REST API
           → Gets list of restaurants immediately

  ASYNCHRONOUS (Kafka Events):
  ─────────────────────────────
  Use when: You don't need an immediate response.
            Other services just need to know something happened.
  Example: Order placed → payment-service charges card
                        → restaurant-service gets notified
                        → delivery-service gets ready
           None of these need to happen synchronously.

  EACH SERVICE:
  ─────────────
  Owns its own database (no shared tables!)
  Has its own Bounded Context model of "Order"
  Communicates only via APIs or Events (never direct DB access)

25. Anti-Patterns to Avoid

╔═══════════════════════════════════════════════════════════════════╗
║  ANTI-PATTERN 1: ANEMIC DOMAIN MODEL                             ║
╠═══════════════════════════════════════════════════════════════════╣
║                                                                   ║
║  What it is: Entities have only getters and setters.             ║
║  All business logic lives in service classes.                    ║
║                                                                   ║
║  ❌ class Order { public void setStatus(String s) { } }          ║
║  ❌ orderService.setStatus(order, "CONFIRMED");                  ║
║                                                                   ║
║  ✅ class Order { public void confirmByRestaurant() { ... } }    ║
║                                                                   ║
║  Why bad: Business rules are scattered, duplicated, invisible.   ║
╠═══════════════════════════════════════════════════════════════════╣
║  ANTI-PATTERN 2: SHARED DATABASE                                  ║
╠═══════════════════════════════════════════════════════════════════╣
║  Two microservices reading/writing the same database tables.      ║
║  Change a table structure → BOTH services break.                 ║
║  Each service MUST own its own data.                             ║
╠═══════════════════════════════════════════════════════════════════╣
║  ANTI-PATTERN 3: GOD AGGREGATE                                    ║
╠═══════════════════════════════════════════════════════════════════╣
║  One aggregate contains everything: Order + Customer +            ║
║  Restaurant + Driver + Menu + Payment.                           ║
║  Every operation locks everything. No concurrency possible.      ║
║  Keep aggregates small — only what MUST be consistent together.  ║
╠═══════════════════════════════════════════════════════════════════╣
║  ANTI-PATTERN 4: SKIPPING UBIQUITOUS LANGUAGE                     ║
╠═══════════════════════════════════════════════════════════════════╣
║  Using technical terms in domain code:                           ║
║  processTransaction(), updateRecord(), flagForReview()           ║
║  These names communicate nothing to the business.                ║
╠═══════════════════════════════════════════════════════════════════╣
║  ANTI-PATTERN 5: LEAKING INFRASTRUCTURE INTO DOMAIN               ║
╠═══════════════════════════════════════════════════════════════════╣
║  ❌ @Entity annotation on your domain Order class                ║
║  ❌ EntityManager injected into domain service                   ║
║  ❌ KafkaTemplate in application service                         ║
║  Domain must be pure Java — no framework dependencies.           ║
╠═══════════════════════════════════════════════════════════════════╣
║  ANTI-PATTERN 6: BYPASSING AGGREGATE ROOT                         ║
╠═══════════════════════════════════════════════════════════════════╣
║  ❌ orderItemRepository.updateQuantity(itemId, 3)                ║
║  ✅ order.updateItemQuantity(itemId, Quantity.of(3))              ║
║  Bypassing the root breaks consistency guarantees.               ║
╚═══════════════════════════════════════════════════════════════════╝

PART D — INTERVIEW QUESTIONS


26. 50 Interview Questions with Answers

Section A: DDD Fundamentals (Questions 1–15)


Q1. What is Domain-Driven Design and why was it created?

Answer: Domain-Driven Design (DDD) is a software design approach introduced by Eric Evans in 2003. It places the business domain at the centre of software design decisions. It was created to solve the problem of growing complexity in enterprise software — where business logic becomes scattered, hard to understand, and expensive to change. DDD provides a set of strategic and tactical patterns to model complex business domains in code that both developers and domain experts can understand.


Q2. What is a Domain and what is a Domain Expert?

Answer: A Domain is the subject area or problem space that your software addresses — the real-world business territory. For a food delivery app, the domain includes customers, restaurants, orders, payments, delivery, and notifications.

A Domain Expert is a person who deeply understands the business — NOT a developer. They are the people whose knowledge you are encoding into software. In our food delivery example: the Operations Manager (order flows), Finance Manager (refund rules), and Logistics Head (delivery routing) are all domain experts.


Q3. What is Ubiquitous Language and why is it important?

Answer: Ubiquitous Language is a shared vocabulary agreed upon by developers and domain experts, used consistently everywhere: in conversations, documents, code, database names, and API endpoints.

It is important because without it, the same concept gets different names in different places — the business says "order", the developer calls it "transaction", the database calls it "tbl_food_requests". This causes miscommunication, bugs, and software that doesn't match what the business needs. With Ubiquitous Language, everyone uses "order" everywhere, and the code reads like the business.


Q4. What is the difference between a Subdomain and a Bounded Context?

Answer:

  • A Subdomain is a logical division of the overall business domain — it exists in the real world, in the problem space. You discover subdomains by analysing the business.
  • A Bounded Context is a defined boundary within which a specific domain model applies — it is a solution space concept. You design bounded contexts.

One Subdomain usually maps to one Bounded Context, but not always. A complex Core Subdomain might need multiple Bounded Contexts, and sometimes a single Bounded Context can span multiple simple subdomains.


Q5. What are Core, Supporting, and Generic Subdomains?

Answer:

  • Core Subdomain: Your competitive advantage. What makes your business unique. You should build this in-house with your best developers and invest most in it. Example: Swiggy's smart delivery routing algorithm.
  • Supporting Subdomain: Necessary for the business but not a differentiator. Build or customise. Example: Restaurant onboarding, customer profile management.
  • Generic Subdomain: Solved problems that every company faces the same way. Buy off-the-shelf or use SaaS. Never build yourself. Example: Authentication, email sending, payments.

Q6. What is an Entity and how does it differ from a Value Object?

Answer:

  • An Entity has a unique identity that persists and never changes, even as its other attributes change. Two entities with the same ID are the same entity. Entities are mutable and have a lifecycle. Example: Order (has OrderId that never changes even when items or status change).
  • A Value Object has no identity — it is defined entirely by its values. Two value objects with the same values are completely identical and interchangeable. Value objects are immutable — they are replaced, never modified. Example: Money (₹100 is ₹100 regardless of which instance).

Key question to ask: "Does it matter WHICH one it is, or just WHAT it is?" If which — it's an Entity. If what — it's a Value Object.


Q7. What is an Aggregate and what is an Aggregate Root?

Answer: An Aggregate is a cluster of Entities and Value Objects that must be kept consistent together as a single unit. It defines a transactional boundary — one transaction should modify one aggregate.

The Aggregate Root is the designated leader of the aggregate — the single entry point for all operations. External code can only access objects inside the aggregate through the root. The root enforces all business invariants and ensures the aggregate is always in a valid state.

Example: Order (root) contains OrderItem entities and Address value object. Nobody can directly modify an OrderItem — they must call methods on Order which then updates items and recalculates totals.


Q8. What are the four golden rules of Aggregates?

Answer:

  1. Reference other aggregates by ID only — never hold object references to other aggregates; only hold their IDs. This prevents tight coupling.
  2. One transaction modifies one aggregate — never update two aggregates in one transaction; use domain events for cross-aggregate updates.
  3. External access only through the Aggregate Root — no one bypasses the root to directly access internal entities.
  4. Keep aggregates small — include only what must be consistent together in one transaction. A huge aggregate causes locking contention and poor performance.

Q9. What is a Domain Event and why should you use them?

Answer: A Domain Event is a record of something significant that happened in the business domain — always in past tense (OrderPlaced, PaymentFailed, OrderDelivered). It is a fact that cannot be undone.

You should use them because:

  • They enable loose coupling between bounded contexts — one context publishes, others subscribe independently.
  • They support eventual consistency — instead of one giant transaction across services, each service updates itself when it hears the relevant event.
  • They provide an audit trail — you can replay events to rebuild state.
  • They make the system reactive — restaurant gets notified, payment gets processed, notifications sent, all independently.

Q10. What is the difference between a Domain Service and an Application Service?

Answer:

  • Domain Service: Contains business logic that doesn't fit naturally in a single entity or value object. It operates on domain objects and speaks the domain language. Has no access to infrastructure (no database, no email). Example: DeliveryFeePolicy.calculate(order, customer, restaurant, distance).
  • Application Service: Orchestrates a complete use case. It loads domain objects from repositories, calls domain logic, saves results, and publishes events. It is deliberately thin — contains NO business rules. It coordinates rather than deciding. Example: PlaceOrderService.execute(command).

Rule of thumb: If the logic involves business rules → Domain Service. If the logic is "load, call domain, save, publish" → Application Service.


Q11. What is a Repository in DDD and where does its interface belong?

Answer: A Repository provides an abstraction over data storage, giving the domain layer the illusion of working with in-memory collections of domain objects. It hides all database details.

Critical point: The Repository interface belongs in the Domain layer (or Application layer) — it defines what operations the domain needs using domain language. The implementation belongs in the Infrastructure layer — it contains JPA, SQL, MongoDB, or whatever storage technology is used.

This means the domain layer NEVER has a compile-time dependency on any database technology. You can swap MySQL for MongoDB by writing a new implementation of the same interface — the domain code never changes.


Q12. What is Context Mapping and what are some common patterns?

Answer: Context Mapping documents how different Bounded Contexts relate and communicate with each other — the "foreign relations map" of your system.

Key patterns:

  • Anti-Corruption Layer (ACL): Translate between a messy external model and your clean domain model at the boundary. Protects your domain.
  • Open Host Service: Publish a well-defined, versioned API for many consumers.
  • Customer-Supplier: Downstream context depends on upstream; upstream must consider downstream needs.
  • Conformist: You have no power to change the upstream, so you adopt their model as-is.
  • Published Language: A common, shared data format (like JSON schema or Protobuf) used for exchange between contexts.

Q13. What is the difference between Strategic DDD and Tactical DDD?

Answer:

  • Strategic DDD deals with the big picture — how to divide and organise the entire system. Tools: Subdomains, Bounded Contexts, Context Maps. Questions: What are the business areas? What's the core? How do they communicate? Done by architects and business leaders before writing code.
  • Tactical DDD deals with implementation details inside each bounded context. Tools: Entities, Value Objects, Aggregates, Domain Events, Repositories, Domain Services. Questions: How do we model this in code? Where do rules live? Done by developers and domain experts during implementation.

Strategic DDD without Tactical DDD gives you good boundaries but messy internals. Tactical DDD without Strategic DDD gives you well-structured code in the wrong places. You need both.


Q14. What is an Anemic Domain Model and why is it bad?

Answer: An Anemic Domain Model is when domain entities are just data containers — they have fields, getters, and setters but no business behaviour. All the business logic lives in service classes instead.

It is bad because:

  • Business rules are scattered across many service classes instead of being encapsulated in the entity they belong to.
  • Logic gets duplicated because multiple services need the same rule.
  • Rules are invisible — you can't tell from the entity what rules apply to it.
  • The code doesn't mirror the business — it's hard for domain experts to validate.
  • Encapsulation is broken — entities can be put into invalid states by any service.

The fix: Move business behaviour INTO the entities. order.cancel() should enforce cancellation rules, not orderService.cancelOrder().


Q15. What is a Factory in DDD and when should you use one?

Answer: A Factory encapsulates complex object creation logic for domain objects — especially Aggregates. When the creation of an Aggregate involves complex validation, assembling multiple child objects, or requires domain knowledge, you extract that logic to a Factory rather than putting it in a constructor or service.

Use a Factory when:

  • Construction requires multiple steps or conditions
  • Creating an Aggregate from one form to another (e.g., Cart → Order)
  • The creation logic is complex enough to deserve a name and a dedicated location

Example: OrderFactory.createFromCart(cart, deliveryAddress) — converts a cart's items into an Order, handling all the transformation logic in one place.


Section B: Hexagonal Architecture (Questions 16–30)


Q16. What is Hexagonal Architecture and who invented it?

Answer: Hexagonal Architecture (also called Ports and Adapters Architecture) was created by Alistair Cockburn in 2005. The central idea is to put all business logic inside a protected core (the hexagon), define contracts (Ports) for everything entering or leaving the core, and implement actual technologies in Adapters at the boundary.

The goal: The business logic should be completely independent of databases, frameworks, UI technologies, and external APIs. You should be able to swap MySQL for MongoDB, REST for gRPC, or Kafka for RabbitMQ without touching a single line of business code.


Q17. What is a Port in Hexagonal Architecture?

Answer: A Port is simply a Java interface. It defines a contract for interaction between the application core and the outside world. There are two types:

  • Driving Ports (Inbound/Primary): Define how the outside world calls into your application. Implemented by Application Services. Called by Driving Adapters (REST, CLI, Kafka consumer). Example: PlaceOrderUseCase, GetOrderUseCase.
  • Driven Ports (Outbound/Secondary): Define what your application needs from the outside world. Implemented by Driven Adapters. Called by Application Services. Example: OrderRepository, PaymentGateway, NotificationSender.

The key distinction: Ports are defined BY the application core. The core never depends on the adapters; adapters depend on the core's ports.


Q18. What is an Adapter in Hexagonal Architecture?

Answer: An Adapter is the implementation that connects a Port to a real technology. It translates between the external world and the application core.

  • Driving Adapters: Translate external calls into Port invocations. A REST controller translates an HTTP POST request into a PlaceOrderCommand and calls PlaceOrderUseCase.execute(). Other driving adapters: GraphQL resolvers, Kafka consumers, CLI commands.
  • Driven Adapters: Implement a Driven Port using real technology. A JPA repository adapter implements OrderRepository using Spring Data JPA and MySQL. Other driven adapters: Kafka publisher, Razorpay payment adapter, Twilio SMS adapter.

Adapters live in the Infrastructure layer and are the only place where framework-specific code belongs.


Q19. What is the Dependency Rule in Hexagonal Architecture?

Answer: The Dependency Rule states that dependencies must always point inward — toward the application core. The core knows nothing about the outside world.

  • Adapters depend on Ports (interfaces in the core).
  • Application Services depend on Domain objects and Driven Ports.
  • Domain objects depend on nothing outside the domain.

If you ever see import org.springframework or import javax.persistence inside your domain/ package, that is an architecture violation. The domain must be pure Java with zero framework dependencies.

This rule is enforced through interfaces: the core defines what it needs (via driven ports/interfaces), and adapters implement those interfaces. This is the Dependency Inversion Principle applied at the architectural level.


Q20. How does Hexagonal Architecture make testing easier?

Answer: Hexagonal Architecture makes testing dramatically easier because you can replace real infrastructure adapters with in-memory test doubles.

Instead of starting a real MySQL database, Kafka cluster, and external payment API for every unit test, you:

  • Create an InMemoryOrderRepository that implements OrderRepository and stores orders in a HashMap.
  • Create a NoOpEventPublisher that implements DomainEventPublisher and does nothing.
  • Create a FakePaymentGateway that always returns success.

Now your PlaceOrderService unit tests run in milliseconds, require zero infrastructure, and test the pure business logic in complete isolation. Integration tests then test the real adapters separately.

Without Hexagonal Architecture, testing the service requires starting the entire stack.


Q21. What is the difference between a Driving Adapter and a Driven Adapter?

Answer:

  • Driving Adapter (Left side / Inbound): The adapter that initiates communication — it calls your application. Examples: REST controller receiving an HTTP request, Kafka consumer receiving a message, CLI receiving a command. The adapter translates the external format into a Port command and calls the use case.
  • Driven Adapter (Right side / Outbound): The adapter that your application calls — it responds to requests from your application. Examples: JPA repository saving to database, Kafka publisher sending events, Razorpay adapter charging a payment. The adapter implements a Driven Port interface.

Driving = "they call us". Driven = "we call them".


Q22. How do you handle cross-cutting concerns (logging, transactions) in Hexagonal Architecture?

Answer: Cross-cutting concerns are handled at the adapter or configuration level, NOT in the domain or application core.

  • Transactions: The Application Service method is annotated with @Transactional (Spring annotation). This annotation stays in the application layer — the domain never knows about transactions.
  • Logging: The Driving Adapter (REST controller) logs incoming requests. The Application Service can log orchestration steps. Infrastructure adapters log technical details. The domain does not log — it just throws exceptions.
  • Security: Applied in the Driving Adapter layer — the REST controller validates JWT tokens before calling the use case.
  • Validation: Basic format validation in adapters. Business validation in domain objects (entities throw exceptions when rules are violated).

Q23. Can you have multiple Driving Adapters for the same application?

Answer: Yes, and this is one of the major benefits of Hexagonal Architecture. Because the application core is exposed through well-defined Port interfaces, you can have many different adapters calling the same use case.

Example for PlaceOrderUseCase:

  • OrderController (REST) — called by mobile apps and web browsers
  • OrderGraphQLResolver (GraphQL) — called by partner applications
  • OrderCLI (Command Line) — called by operations team scripts
  • OrderKafkaConsumer (Kafka) — called by internal services publishing messages

All of them call the same PlaceOrderUseCase.execute() method. The business logic runs once and is correct for all callers. Adding a new way to call the system requires only a new adapter — the core is untouched.


Q24. What is the relationship between the Repository pattern in DDD and Ports in Hexagonal Architecture?

Answer: The Repository interface in DDD IS a Driven Port in Hexagonal Architecture. They are the same concept viewed from two different angles.

  • DDD perspective: The Repository provides domain objects with the illusion of working with in-memory collections, hiding storage details.
  • Hexagonal perspective: The Repository interface is a Driven Port that defines what the application core needs from storage infrastructure.

The interface (OrderRepository) lives in the application core (domain or application layer). The implementation (JpaOrderRepository) is a Driven Adapter in the infrastructure layer. This is exactly the Ports and Adapters pattern: the port is defined by the core, the adapter implements it in the infrastructure.


Q25. What is an Anti-Corruption Layer and how does it relate to Hexagonal Architecture?

Answer: An Anti-Corruption Layer (ACL) is a DDD pattern for protecting your clean domain model from an external messy model. In Hexagonal Architecture, it is implemented as a Driven Adapter.

When your application needs to call an external API (like Razorpay for payments), that API has its own data model, terminology, and conventions that may not match your domain. The Driven Adapter acts as the ACL:

  1. Your application calls the PaymentGateway Driven Port (your clean interface).
  2. The RazorpayAdapter (Driven Adapter / ACL) translates your domain's PaymentDetails into Razorpay's ChargeRequest format.
  3. It calls Razorpay's API.
  4. It translates Razorpay's ChargeResponse back into your domain's PaymentResult.

Your domain never knows Razorpay exists. If you switch to Stripe, you write a new adapter — the domain is untouched.


Q26. Why should the Domain layer have zero framework dependencies?

Answer: The Domain layer should have zero framework dependencies for several critical reasons:

  1. Testability: Domain objects can be instantiated in any test with new Order(...) — no Spring context, no mocks, no container needed. Tests run in milliseconds.
  2. Longevity: Frameworks change. Spring 5 → Spring 6 introduces breaking changes. If your domain is pure Java, you can upgrade the framework without touching business logic.
  3. Clarity: Framework annotations like @Entity, @Column, @JsonProperty are noise in domain code. They obscure what matters: the business rules.
  4. Portability: A domain layer with zero dependencies can be used in any Java project regardless of the framework choices.
  5. Separation of concerns: The domain's job is to model business rules. Framework configuration is an infrastructure concern.

Q27. What problem does Hexagonal Architecture solve that layered architecture doesn't?

Answer: Traditional layered architecture (Presentation → Service → Repository → Database) still allows infrastructure details to leak upward. Services often directly use JPA EntityManager, REST template, or Kafka template — making them impossible to test without full infrastructure.

Hexagonal Architecture solves this by:

  • Inverting dependencies — the core defines what it needs (via ports), infrastructure implements it
  • Making ALL external dependencies optional and swappable
  • Enabling the core to be tested completely in isolation with test doubles
  • Making it physically impossible for infrastructure code to leak into the core (if you follow the rule that domain packages import nothing from infrastructure packages)

The key insight: in layered architecture, the flow is top-down and all layers know about the database. In Hexagonal, the core knows nothing — only adapters do.


Q28. How do you structure a Spring Boot project to implement Hexagonal Architecture?

Answer: The project structure enforces the architecture through package boundaries:

src/main/java/com/company/service/
├── domain/           ← Zero Spring/JPA dependencies. Pure Java.
│   ├── model/        ← Entities, Value Objects, Aggregates
│   └── service/      ← Domain Services
├── application/      ← Thin orchestration. @Service annotations allowed.
│   ├── port/in/      ← Driving Ports (interfaces for use cases)
│   ├── port/out/     ← Driven Ports (interfaces for repositories, gateways)
│   └── service/      ← Use Case implementations (implements Driving Ports)
└── infrastructure/   ← All framework code lives here.
    ├── adapter/in/   ← @RestController, @KafkaListener (Driving Adapters)
    └── adapter/out/  ← @Repository, Kafka producers (Driven Adapters)

You can enforce this with architecture tests using ArchUnit:

java
@ArchTest
static final ArchRule domainMustNotDependOnInfrastructure =
    noClasses().that().resideInAPackage("..domain..")
        .should().dependOnClassesThat()
        .resideInAPackage("..infrastructure..");

Q29. What is the difference between application/port/in and application/port/out?

Answer:

  • application/port/in contains Driving Ports — interfaces that represent use cases your application supports. They define what your application can DO. Example: PlaceOrderUseCase, CancelOrderUseCase. These are implemented by your Application Services and called by Driving Adapters.
  • application/port/out contains Driven Ports — interfaces that represent external capabilities your application NEEDS. Example: OrderRepository, PaymentGateway, NotificationSender. These are defined by your core and implemented by Driven Adapters in infrastructure.

A simple memory trick: port/in = things coming IN to the application (use cases). port/out = things going OUT of the application (repositories, external APIs).


Q30. How does Hexagonal Architecture support the SOLID principles?

Answer:

  • Single Responsibility: Each adapter has one job — translate one specific technology to/from the core. Domain classes have one job — model business rules.
  • Open/Closed: Adding a new way to call the application (e.g., gRPC) is done by adding a new Driving Adapter without modifying the core.
  • Liskov Substitution: Any implementation of OrderRepository (JPA, MongoDB, InMemory) can be substituted without affecting the application logic.
  • Interface Segregation: Ports are narrow, focused interfaces — PlaceOrderUseCase for placing, CancelOrderUseCase for cancelling. Not one fat service interface.
  • Dependency Inversion: The high-level domain defines interfaces (ports). Low-level adapters implement them. High-level never depends on low-level.

Section C: Combined DDD + Hexagonal (Questions 31–40)


Q31. How do DDD and Hexagonal Architecture complement each other?

Answer: DDD and Hexagonal Architecture solve different but complementary problems:

  • DDD answers: "What should the software model?" — it gives you the vocabulary (Ubiquitous Language), the structure (Bounded Contexts), and the patterns to correctly represent business rules in code (Entities, Aggregates, Domain Events).
  • Hexagonal Architecture answers: "How do we protect that model from technology details?" — it gives you the structure to keep your DDD domain model pure, testable, and independent of frameworks.

Without DDD, Hexagonal gives you a clean structure but no guidance on how to model the business inside that structure. Without Hexagonal, DDD models get polluted by JPA annotations, Spring dependencies, and HTTP concerns. Together: your business is correctly modelled AND completely protected from technology.


Q32. How does a Bounded Context map to Hexagonal Architecture?

Answer: Each Bounded Context is implemented as one complete Hexagonal module or microservice. The mapping is direct:

  • The Bounded Context defines what domain objects exist and what they mean (Ubiquitous Language).
  • The Hexagon (application core) contains those domain objects — Entities, Aggregates, Value Objects, Domain Services — along with the Ports that define how the context communicates.
  • Driving Adapters represent how the outside world talks to this context (REST API, Kafka input, gRPC).
  • Driven Adapters represent how this context talks to infrastructure (its own database, messaging, external APIs).

Communication between Bounded Contexts happens through the Driving Adapters of one context (calling another's REST API) or through published Domain Events consumed by Driving Adapters (Kafka consumers).


Q33. Where do Domain Events get published in Hexagonal Architecture?

Answer: Domain Events are published through a Driven Port.

  1. The Domain Aggregate creates Domain Events internally during operations and stores them in a list (domainEvents).
  2. The Application Service (after saving the aggregate) calls DomainEventPublisher.publish(events) — this is a Driven Port (an interface).
  3. The KafkaDomainEventPublisher Driven Adapter implements this port and sends events to Kafka.

The Domain and Application layers never know about Kafka. They only know about the DomainEventPublisher interface. You can switch from Kafka to RabbitMQ by writing a new adapter — no domain or application code changes.


Q34. How do you prevent the Aggregate model from becoming a JPA entity?

Answer: This is a critical concern. The solution is to have TWO separate classes:

  1. Domain model (Order.java): Pure Java class in domain/model/. No JPA annotations. Contains business rules. This is the Aggregate Root.
  2. Persistence model (OrderJpaEntity.java): Spring/JPA annotated class in infrastructure/persistence/. Has @Entity, @Column, @Table. This is only for database mapping.

A Mapper (OrderPersistenceMapper.java) in the infrastructure layer converts between them:

  • OrderOrderJpaEntity (for saving)
  • OrderJpaEntityOrder (for loading)

This ensures your domain model is pure and your database schema can evolve independently of your domain model. The cost is the mapper code, but the benefit is a clean, testable domain.


Q35. How do you handle validation in the DDD + Hexagonal model?

Answer: Validation happens at multiple levels, each with a specific responsibility:

  1. Input format validation (Driving Adapter level): The REST controller validates that the JSON has all required fields, correct types, valid lengths. Use Bean Validation (@NotNull, @Size). This is HTTP-specific validation.

  2. Value Object validation (Domain level): Value Objects validate themselves in their constructor. new Money(-50, "INR") throws immediately. new Address(null, "street", ...) throws immediately. This is business rule validation.

  3. Business rule validation (Entity/Aggregate level): The Aggregate Root validates business state in every operation. order.cancel() checks if the order is already shipped before allowing cancellation.

  4. Cross-aggregate validation (Domain Service level): DeliveryFeePolicy.calculate() validates cross-cutting business rules involving multiple objects.

Never put business rules in adapters. Never put format validation in the domain.


Q36. How do you implement optimistic locking in this architecture?

Answer: Optimistic locking prevents concurrent modification conflicts. In DDD + Hexagonal:

  1. Domain: Add a version field to the Aggregate Root (just a long field — no JPA annotations in domain).

  2. Persistence Model: The OrderJpaEntity has @Version Long version which JPA uses for optimistic locking.

  3. Mapper: When mapping JpaEntity → Domain, copy the version. When mapping Domain → JpaEntity, copy it back.

  4. Conflict handling: JPA throws OptimisticLockException in the adapter. The adapter catches it and translates it to a domain-meaningful exception (e.g., OrderConcurrentModificationException) that the Application Service can handle.

The domain never knows about locking mechanisms — it just has a version number field.


Q37. How does CQRS relate to DDD and Hexagonal Architecture?

Answer: CQRS (Command Query Responsibility Segregation) separates the write model from the read model. It integrates naturally with both DDD and Hexagonal:

  • Commands (PlaceOrder, CancelOrder) go through the full DDD stack: Application Service → Aggregate → Repository → Database. This is the write side.
  • Queries (GetOrderDetails, ListOrdersForCustomer) bypass the Aggregate entirely and go directly to a read-optimised model — often a simple DTO from a database query or a dedicated read table.

In Hexagonal Architecture:

  • Commands use PlaceOrderUseCase (Driving Port) → PlaceOrderService → Domain → OrderRepository (Driven Port).
  • Queries use GetOrderUseCase (Driving Port) → GetOrderServiceOrderReadRepository (Driven Port, returns DTOs directly) → optimised query.

CQRS improves performance by not loading full aggregates for read operations and allows separate scaling of read and write sides.


Q38. How do you handle long-running processes or sagas in DDD + Hexagonal?

Answer: Long-running processes (like the complete food delivery lifecycle: Place → Confirm → Prepare → Dispatch → Deliver) are handled using the Saga pattern.

In DDD + Hexagonal:

  1. Each step is an independent transaction modifying one Aggregate.
  2. Completion of each step publishes a Domain Event.
  3. The next step is triggered by that event.
  4. A Process Manager (or Saga Orchestrator) is a special domain object that tracks the overall state of the saga and decides what happens next.

The Process Manager:

  • Is itself an Aggregate (has identity, has state)
  • Lives in the Domain layer
  • Reacts to events (listens via Driving Adapter → Application Service → Process Manager)
  • Publishes commands or events to trigger next steps

For compensation (rollback): Each step has a compensating action. If payment fails after restaurant confirmed, the Process Manager triggers order.cancel() and restaurant.releaseOrder().


Q39. How do you test an Application Service in Hexagonal Architecture?

Answer: Application Services are tested using unit tests with test doubles (fakes) for all Driven Ports:

java
class PlaceOrderServiceTest {

    PlaceOrderService service;
    InMemoryOrderRepository orderRepo = new InMemoryOrderRepository();
    InMemoryCustomerRepository customerRepo = new InMemoryCustomerRepository();
    FakeEventPublisher eventPublisher = new FakeEventPublisher();

    @BeforeEach
    void setUp() {
        service = new PlaceOrderService(
            orderRepo, customerRepo, restaurantRepo,
            new DeliveryFeePolicy(), eventPublisher
        );
        customerRepo.save(Customer.of("CUST-1", "Priya", false));
    }

    @Test
    void shouldPlaceOrderSuccessfully() {
        PlaceOrderResult result = service.execute(validCommand());
        assertThat(result.orderId()).isNotBlank();
        assertThat(result.deliveryFee()).isEqualTo(0.0); // Free since subtotal > ₹299
        assertThat(orderRepo.findById(OrderId.of(result.orderId()))).isPresent();
        assertThat(eventPublisher.publishedEvents()).hasSize(1);
        assertThat(eventPublisher.publishedEvents().get(0)).isInstanceOf(OrderPlacedEvent.class);
    }
}

No Spring context. No database. No Kafka. Test runs in milliseconds and tests pure business logic.


Q40. What are the trade-offs of using DDD and Hexagonal Architecture?

Answer:

Benefits:

  • Extremely testable — domain logic tested without infrastructure
  • Clear separation of concerns — everyone knows where to find what
  • Technology independence — swap frameworks, databases, or APIs without touching business logic
  • Explicit business rules — easy to validate with domain experts
  • Evolutionary architecture — add new adapters without breaking existing

Costs:

  • More code initially: Two models for domain (domain object + JPA entity), mapper classes, port interfaces, adapter classes. Estimated 30-40% more classes.
  • Learning curve: The team must understand and follow the patterns consistently. One violation undermines the whole architecture.
  • Overhead for simple CRUD: For simple Create/Read/Update/Delete operations with no real business logic, DDD + Hexagonal is overkill. Use it for complex business domains.
  • Mapper maintenance: Keeping domain object ↔ persistence model mappers in sync requires discipline.

Rule of thumb: Apply DDD + Hexagonal to the Core Subdomain where complexity justifies the investment. Use simpler patterns for Generic and Supporting Subdomains.


Section D: Advanced & Scenario Questions (Questions 41–50)


Q41. You inherit a codebase with an Anemic Domain Model. How do you migrate it to a rich DDD model?

Answer: This is a refactoring journey that must be done incrementally:

  1. Identify the core domain: Focus on the most valuable part first — don't try to refactor everything.
  2. Establish Ubiquitous Language: Run workshops with domain experts to agree on correct terminology. Create a glossary.
  3. Identify Aggregates: Group related entities that must be consistent together.
  4. Move logic into entities: Take each business rule from service classes and move it into the entity or value object it belongs to. One rule at a time. Each move is a separate commit.
  5. Introduce Value Objects: Find primitive obsession (String for phone numbers, double for money). Create Value Objects that self-validate.
  6. Extract Domain Services: Identify logic that involves multiple entities and extract it to a named Domain Service.
  7. Write tests first: Before moving any logic, write a test for it. This ensures you don't break behaviour.
  8. Strangler Fig Pattern: Keep old code working while you build new DDD model alongside it. Gradually switch traffic over.

This takes months in a large codebase. Prioritise by business value.


Q42. Two developers on your team argue about whether Customer should be inside the Order Aggregate. How do you resolve this?

Answer: The answer is almost certainly no — Customer should NOT be inside the Order Aggregate. Here's how to resolve it:

Apply Aggregate rules:

  1. Rule: Must they be consistent in one transaction? When I place an order, does the Customer object need to change atomically with the Order? No — the Order is created, but the Customer's profile doesn't change.
  2. Rule: Keep aggregates small. Including Customer makes the Order aggregate huge — it has customer history, addresses, preferences. This creates locking contention.
  3. Rule: Reference by ID only. Order should only hold CustomerId, not the Customer object.

The resolution: Order holds CustomerId. If the Application Service needs Customer data during order placement (e.g., to check credit), it loads Customer separately via CustomerRepository before creating the Order. After the Order is created, Customer and Order are independent aggregates that only share the ID reference.

Use a Domain Service (CustomerCreditService) if business logic requires reasoning about both Customer and Order together.


Q43. Your team wants to know when to use synchronous REST calls vs. asynchronous events between bounded contexts. How do you decide?

Answer: Use this decision framework:

Use synchronous REST when:

  • The caller needs an immediate response to proceed (query: "Show me restaurant menu before I order")
  • The operation must succeed or fail atomically from the user's perspective
  • The latency of async would create a poor user experience

Use asynchronous events when:

  • The caller doesn't need to wait for the outcome (place order → payment happens in background)
  • Multiple services need to react to the same event (order placed → payment, restaurant notification, analytics, fraud check all react independently)
  • The downstream services should be independently deployable and scalable
  • You want temporal decoupling — downstream service can be down and catch up later

General rule for food delivery:

  • REST: "What restaurants are near me?" "Is this restaurant open?" (need immediate answer)
  • Events: "Order placed" → triggers payment, restaurant notification, delivery assignment (don't need to wait for all simultaneously)

The goal of events is loose coupling and resilience — if the notification service is down, the order still succeeds.


Q44. How would you handle a scenario where a business rule changes in the domain after three years of production use?

Answer: This is where DDD pays off the most. With a well-structured DDD + Hexagonal architecture:

  1. Locate the rule: Because business rules live in the domain layer (in entities or domain services), you know exactly where to look. The DeliveryFeePolicy class contains all delivery fee rules. There's only one place.

  2. Read the current rule: The method reads almost like plain English — a domain expert can validate it.

  3. Change the rule: Modify the method in DeliveryFeePolicy. The change is one class, in one file, in one method.

  4. Existing tests catch regressions: Your unit tests for DeliveryFeePolicy tell you exactly which test scenarios the new rule affects.

  5. Write new tests: Add tests for the new rule behaviour.

  6. No infrastructure changes: The JPA adapter, Kafka publisher, REST controller — all unchanged. They know nothing about delivery fee logic.

Compare this to an anemic model where the same rule is scattered across OrderController, PaymentService, OrderService, NotificationService — you'd need to hunt it down and change it in multiple places, likely missing one and creating a bug.


Q45. A new requirement comes in: support both REST API and a gRPC API for the same order placement feature. How do you handle this with Hexagonal Architecture?

Answer: This is one of Hexagonal Architecture's greatest strengths — adding a new driving adapter requires zero changes to the application core.

Steps:

  1. The PlaceOrderUseCase Driving Port already exists — it defines the contract for placing an order.
  2. The PlaceOrderService already implements it — all business logic is there.
  3. Create a new Driving Adapter: OrderGrpcController that implements the gRPC service interface, translates the Protobuf request into PlaceOrderCommand, and calls placeOrderUseCase.execute(command).
  4. Configure the gRPC server in Spring Boot.

That's it. The domain, application service, repositories, event publishers — all untouched. The same business logic now serves both REST and gRPC clients.

You can apply the same approach for: GraphQL, CLI, Kafka consumer, WebSocket — all without touching the core.


Q46. What is the role of the @Transactional annotation in Hexagonal Architecture, and where should it live?

Answer: @Transactional is a Spring infrastructure concern. Its proper location in Hexagonal Architecture is the Application Service layer.

Reasoning:

  • The domain/ layer contains pure Java — zero Spring annotations. No @Transactional there.
  • The infrastructure/adapter/out/ (JPA adapter) shouldn't manage transactions — it shouldn't decide what constitutes a unit of work.
  • The Application Service orchestrates the use case: load → domain logic → save → publish events. This entire sequence should be one atomic operation. Therefore @Transactional goes on the Application Service method.
java
@Service
@Transactional  // ← This is the correct location
public class PlaceOrderService implements PlaceOrderUseCase {
    public PlaceOrderResult execute(PlaceOrderCommand command) {
        // This entire method is one transaction
        Order order = Order.place(...);
        orderRepository.save(order);
        // If exception here, the save above is rolled back
        eventPublisher.publish(order.getDomainEvents()); // Publish after commit
    }
}

One caveat: Domain Events should be published AFTER the transaction commits (not inside it), otherwise the event goes out but the transaction rolls back. Use Spring's @TransactionalEventListener(phase = AFTER_COMMIT) or the Outbox Pattern for guaranteed event delivery.


Q47. How would you explain DDD and Hexagonal Architecture to a junior developer in one minute?

Answer: Here's a clear, beginner-friendly explanation:

"Imagine you're building an online food delivery app. DDD says: let the business dictate how you write code. When the business says 'customer places an order', your code should say order.place() — not updateRecord() or processTxn(). Use the same words the business uses, everywhere.

Now, the business logic is valuable and fragile. Hexagonal Architecture says: protect it. Put it in a box (the hexagon). Define doorways into the box — these are Ports (interfaces). Write Adapters to connect real technology (databases, REST APIs, Kafka) to those doorways.

The result: your business rules live in pure Java that any developer can read. Tests run in milliseconds. You can swap MySQL for MongoDB by writing a new Adapter — your business logic never changes. That's the power of combining them."


Q48. What is the Outbox Pattern and when would you use it with Domain Events?

Answer: The Outbox Pattern solves the problem of ensuring Domain Events are reliably published even if the application crashes after saving to the database but before publishing to Kafka.

The problem:

1. Order saved to database ✅
2. Application crashes 💥
3. OrderPlacedEvent never published ❌
→ Payment service never charges. Restaurant never notified.

The solution:

  1. Within the same transaction as saving the Order, also save the event to an outbox table in the same database.
  2. A separate background process (Outbox Publisher) polls the outbox table and publishes events to Kafka.
  3. Once published, mark the outbox record as PUBLISHED.

In Hexagonal Architecture:

  • DomainEventPublisher Driven Port has an implementation that saves to the outbox table instead of Kafka directly.
  • A @Scheduled component (Driving Adapter) reads the outbox and calls Kafka (Driven Adapter).

Use the Outbox Pattern when: events must be published reliably (eventual consistency is critical), and you can't use distributed transactions (XA/2PC).


Q49. How does Event Sourcing differ from the standard DDD approach?

Answer: In standard DDD, you store the current state of an Aggregate in a database table. The history of how it got there is lost.

In Event Sourcing, you store every Domain Event that ever happened to the Aggregate. The current state is derived by replaying all events from the beginning.

Standard DDD:

  • orders table has one row per order showing current state (status=DELIVERED)
  • Fast to query current state
  • No history of intermediate states

Event Sourcing:

  • order_events table has rows: OrderPlaced, OrderConfirmed, OrderDispatched, OrderDelivered
  • Current state = replay all events
  • Complete audit trail — you know exactly what happened and when
  • Can time-travel — replay to any point in time
  • More complex: requires event store, projection builders, eventual consistency

In Hexagonal Architecture: The OrderRepository Driven Port interface is the same. The implementation changes — instead of saving the Order entity, it saves the list of Domain Events. When loading, it replays events to reconstruct the Order.

Use Event Sourcing for: financial systems (complete audit required), complex business processes where history matters, or when you need to time-travel through state.


Q50. How would you design the entire food delivery system using DDD + Hexagonal Architecture in a system design interview?

Answer: A complete answer to this question:

Step 1 — Strategic DDD:

  • Identify Bounded Contexts: Order, Restaurant, Delivery, Payment, Customer Profile, Notification, Analytics
  • Classify: Core = Order Management, Delivery Routing, Dynamic Pricing. Supporting = Restaurant Mgmt, Customer Profile. Generic = Notification (Twilio), Auth (Auth0), Payment (Razorpay).
  • Context Map: Order publishes events consumed by Restaurant, Payment, Delivery, Notification.

Step 2 — Each Bounded Context = One Hexagonal Microservice:

  • order-service: Driving adapters: REST API, Kafka consumer. Domain: Order aggregate, OrderItem, DeliveryFeePolicy. Driven adapters: JPA (PostgreSQL), Kafka publisher, HTTP to delivery-service for distance.
  • restaurant-service: Driving adapters: REST API, Kafka consumer (order.placed). Domain: Restaurant, Menu. Driven adapters: MongoDB, Kafka publisher.
  • payment-service: Driving adapters: Kafka consumer (order.placed). Domain: Payment aggregate. Driven adapters: Razorpay ACL adapter, PostgreSQL.
  • delivery-service: Domain: Delivery assignment, routing algorithm. Driven adapters: PostgreSQL, Redis, Google Maps ACL adapter.

Step 3 — Communication:

  • Synchronous REST: Customer app → order-service; order-service → restaurant-service for menu
  • Async Events: OrderPlaced → triggers Payment, Restaurant, Notification, Delivery independently

Step 4 — Data:

  • Each service owns its own database (no shared tables)
  • Order service: PostgreSQL (orders, order_items)
  • Restaurant service: MongoDB (restaurants, menus)
  • Delivery service: PostgreSQL + Redis (real-time location tracking)

Step 5 — Testing strategy:

  • Domain unit tests: No infrastructure, pure business rules
  • Application unit tests: In-memory adapters, test use cases
  • Integration tests: Real database in Docker, test adapter implementations
  • Contract tests: Ensure event schemas match between services

This design is maintainable, testable, independently deployable, and aligned with the business — the hallmark of successful DDD + Hexagonal Architecture.

50 Multiple Choice Questions

Domain-Driven Design & Hexagonal Architecture

Instructions: Each question has four options (A–D). The correct answer and a detailed explanation are provided below each question. Questions range from beginner to advanced level.


SECTION 1 — DDD Fundamentals (Q1–Q15)


Q1. Who introduced Domain-Driven Design and in which year?

  • A) Martin Fowler, 1996
  • B) Eric Evans, 2003
  • C) Alistair Cockburn, 2005
  • D) Robert Martin, 2008

Answer: B
Eric Evans introduced DDD in his 2003 book "Domain-Driven Design: Tackling Complexity in the Heart of Software." Alistair Cockburn introduced Hexagonal Architecture in 2005. Martin Fowler and Robert Martin are known for other patterns.


Q2. What is a Domain in the context of DDD?

  • A) The database schema that stores business data
  • B) The programming language and framework used to build the system
  • C) The real-world subject area and problem space the software addresses
  • D) The network infrastructure on which the software runs

Answer: C
The Domain is the real-world business territory — the problem space your software must solve. For a food delivery app, it includes customers, restaurants, orders, delivery, payments, and everything related to the business. It has nothing to do with technology choices.


Q3. What is Ubiquitous Language?

  • A) A programming language that runs on all platforms
  • B) A shared vocabulary agreed upon by developers and domain experts, used everywhere
  • C) The language used to write database migration scripts
  • D) An international standard for API documentation

Answer: B
Ubiquitous Language is a shared vocabulary used consistently by everyone — developers, domain experts, testers, managers — in all places: conversations, documents, code, database names, and API endpoints. It ensures the code mirrors the business without translation gaps.


Q4. Which of the following is an example of applying Ubiquitous Language correctly?

  • A) Business says "place an order", developer writes createTransaction()
  • B) Business says "place an order", developer writes order.place()
  • C) Business says "place an order", developer writes insertRecord()
  • D) Business says "place an order", developer writes processRequest()

Answer: B
order.place() uses the exact business term "place" on the exact business concept "order." Options A, C, and D all introduce technical synonyms that don't match the business language, creating a translation layer that causes confusion and bugs.


Q5. What is a Core Subdomain?

  • A) The part of the system that handles authentication and login
  • B) The database layer that stores all business data
  • C) The business area that represents your unique competitive advantage — build in-house
  • D) A third-party library used across the entire application

Answer: C
A Core Subdomain is the part of the business that makes you unique and differentiates you from competitors. For Swiggy, their smart delivery routing algorithm is a Core Subdomain. Core Subdomains should be built in-house with the best developers and receive the most investment.


Q6. A company uses Stripe for payment processing and Twilio for SMS. These fall into which subdomain type?

  • A) Core Subdomain
  • B) Supporting Subdomain
  • C) Generic Subdomain
  • D) Integration Subdomain

Answer: C
Payment processing and SMS sending are solved problems that work the same way for every business. They are Generic Subdomains — you should buy a SaaS solution (Stripe, Twilio) rather than build them yourself. Building these in-house wastes valuable resources that should be spent on Core Subdomains.


Q7. What makes an Entity different from a Value Object?

  • A) Entities are stored in databases; Value Objects are not
  • B) Entities have unique identity that persists; Value Objects are defined by their values alone
  • C) Entities can have methods; Value Objects cannot
  • D) Entities are immutable; Value Objects are mutable

Answer: B
The fundamental difference is identity. An Entity has a unique ID that persists throughout its lifecycle — even if all other attributes change, the entity remains the same. A Value Object has no identity — two objects with the same values are completely interchangeable. Note that D has it backwards — Value Objects are immutable, not Entities.


Q8. Which of the following is best modelled as a Value Object?

  • A) A customer's bank account
  • B) A delivery partner assigned to an order
  • C) A monetary amount (e.g., ₹499.00 INR)
  • D) A restaurant's profile

Answer: C
A monetary amount like ₹499.00 INR is a Value Object — you don't track which specific "instance" of ₹499 you have. Any ₹499 is interchangeable with any other ₹499. Options A, B, and D all have unique identities — you track a specific account, a specific partner, and a specific restaurant.


Q9. What is an Aggregate in DDD?

  • A) A database view that combines data from multiple tables
  • B) A group of Entities and Value Objects treated as one unit, with one Aggregate Root
  • C) A collection of microservices that share one database
  • D) An interface that all domain objects must implement

Answer: B
An Aggregate is a cluster of Entities and Value Objects that must be kept consistent together. It has a single Aggregate Root that is the only entry point for all external interactions. The Aggregate enforces all consistency rules (invariants) within its boundary.


Q10. Which statement about the Aggregate Root is CORRECT?

  • A) Every Entity inside an Aggregate can be accessed directly from outside
  • B) The Aggregate Root is always a Value Object
  • C) External code can only access the Aggregate through the Aggregate Root
  • D) There can be multiple Aggregate Roots within one Aggregate

Answer: C
The Aggregate Root is the single gatekeeper — all external access must go through it. This allows the root to enforce all business rules and maintain consistency. There is exactly ONE Aggregate Root per Aggregate, and it is always an Entity (not a Value Object) because it needs identity.


Q11. Your Order aggregate has OrderItem entities inside it. A service wants to update the quantity of an item. Which approach follows DDD correctly?

  • A) orderItemRepository.updateQuantity(itemId, newQty)
  • B) orderItem.setQuantity(newQty)
  • C) order.updateItemQuantity(itemId, Quantity.of(newQty))
  • D) db.executeUpdate("UPDATE order_items SET qty=? WHERE id=?", newQty, itemId)

Answer: C
All changes must go through the Aggregate Root (order). This allows the Order to enforce business rules (e.g., cannot update quantity on a delivered order) and maintain consistency (e.g., recalculate the total after the change). Options A, B, and D all bypass the root, breaking the consistency guarantee.


Q12. What is the correct rule for how Aggregates should reference each other?

  • A) Aggregates should always hold object references to other Aggregates
  • B) Aggregates should only reference other Aggregates by their ID
  • C) Aggregates should never communicate with each other
  • D) Aggregates can share internal entities across boundaries

Answer: B
Aggregates reference each other by ID only — never by object reference. For example, Order holds CustomerId, not a Customer object. This prevents tight coupling between aggregates and allows them to evolve independently. Holding an object reference means loading one Aggregate forces loading another, creating performance and consistency issues.


Q13. What is a Domain Event?

  • A) An exception thrown when a domain rule is violated
  • B) A record of something significant that happened in the business, stated in past tense
  • C) A request from the user interface to perform an operation
  • D) A database trigger that fires when a record is modified

Answer: B
A Domain Event represents a business fact that has already occurred — always stated in past tense (OrderPlaced, PaymentFailed, OrderDelivered). It is immutable (it happened — it cannot be undone) and can trigger reactions in other parts of the system. An exception (A) is a programming construct. A user request (C) is a Command, not an Event.


Q14. What is the key difference between a Domain Service and an Application Service?

  • A) Domain Services talk to databases; Application Services do not
  • B) Domain Services contain business logic; Application Services orchestrate use cases without business rules
  • C) Application Services are tested; Domain Services are not
  • D) Domain Services use REST; Application Services use Kafka

Answer: B
This is a critical distinction. Domain Services contain business logic that doesn't fit on a single entity (e.g., DeliveryFeePolicy.calculate()). Application Services are thin orchestrators — they load domain objects, call domain logic, save results, and publish events. Application Services have zero business rules; they only coordinate.


Q15. Which of the following describes the Anemic Domain Model anti-pattern?

  • A) Domain entities are too large and contain too many responsibilities
  • B) Entities have only getters and setters; all business logic lives in service classes
  • C) Domain services communicate directly with the database
  • D) Aggregates reference each other through object references

Answer: B
The Anemic Domain Model is when your entities are just data bags — they have fields and getters/setters but no behaviour. All business logic is scattered across service classes. This breaks encapsulation, duplicates logic, and makes the code hard to understand because the business rules are invisible in the data structures.


SECTION 2 — Hexagonal Architecture (Q16–Q30)


Q16. What is another name for Hexagonal Architecture?

  • A) Layered Architecture
  • B) Clean Architecture
  • C) Ports and Adapters Architecture
  • D) Microservices Architecture

Answer: C
Hexagonal Architecture is also called Ports and Adapters Architecture — the two names refer to the same pattern coined by Alistair Cockburn in 2005. "Hexagonal" refers to the visual shape used in diagrams. Clean Architecture (Uncle Bob) is a related but distinct pattern.


Q17. What is a Port in Hexagonal Architecture?

  • A) A network port number (e.g., 8080) used by the application
  • B) A Java interface that defines a contract between the application core and the outside world
  • C) A database connection pool configuration
  • D) A specific URL endpoint in a REST API

Answer: B
A Port is simply a Java interface. It defines a contract without specifying the implementation. Driving Ports define how the outside world calls into your application. Driven Ports define what your application needs from the outside world. The term "port" in Hexagonal Architecture has nothing to do with network ports.


Q18. What is a Driving Adapter (also called a Primary Adapter)?

  • A) An adapter that connects your application to a database
  • B) A component that translates external calls into Driving Port invocations
  • C) A class that implements a repository interface
  • D) A Kafka producer that publishes domain events

Answer: B
A Driving Adapter (Primary/Inbound) translates an external format into a call to a Driving Port. Examples: REST controller translates HTTP request → PlaceOrderUseCase.execute(), Kafka consumer translates a Kafka message → CancelOrderUseCase.execute(). Options C and D are Driven (outbound) adapters.


Q19. Which of the following is a Driven Adapter (Secondary Adapter)?

  • A) A REST controller receiving HTTP requests
  • B) A GraphQL resolver responding to queries
  • C) A JPA repository implementation saving orders to MySQL
  • D) A Kafka consumer listening for incoming messages

Answer: C
Driven Adapters (Secondary/Outbound) are called BY your application and connect to external systems. A JPA repository adapter implements OrderRepository (Driven Port) and executes SQL against MySQL. Options A, B, and D are all Driving (inbound) adapters — they call into your application.


Q20. What is the Dependency Rule in Hexagonal Architecture?

  • A) All layers can depend on each other freely
  • B) Dependencies point outward — the core depends on adapters
  • C) Dependencies point inward — the core never depends on adapters
  • D) Only adapters can have dependencies; the core has none

Answer: C
The Dependency Rule states that dependencies always point inward toward the application core. Adapters depend on Ports (core interfaces). Application Services depend on Domain objects and Driven Ports. The Domain depends on nothing external. The core NEVER imports from infrastructure or adapter packages.


Q21. Why does Hexagonal Architecture make unit testing easier?

  • A) It eliminates the need for unit tests
  • B) It allows you to replace real infrastructure with in-memory test doubles
  • C) It generates test cases automatically
  • D) It forces all tests to use a real database

Answer: B
Because the application core depends only on Port interfaces, you can swap real adapters for lightweight test doubles. An InMemoryOrderRepository that stores orders in a HashMap replaces JPA. A NoOpEventPublisher replaces Kafka. The Application Service and Domain tests run in milliseconds with zero infrastructure.


Q22. Where should framework-specific code (e.g., @RestController, @Entity, @KafkaListener) live in Hexagonal Architecture?

  • A) In the domain layer alongside business logic
  • B) In the application service layer
  • C) In the infrastructure adapter layer only
  • D) It can be placed anywhere for convenience

Answer: C
Framework-specific annotations belong exclusively in the infrastructure adapter layer. The domain layer must be pure Java with zero framework dependencies. If you see @Entity or @Column in your domain/model/ package, that is an architecture violation. Framework code in the domain creates tight coupling that makes testing and migration painful.


Q23. You need to support both REST API and gRPC for the same business operation. What changes in Hexagonal Architecture?

  • A) The entire application must be rewritten
  • B) The domain model must be duplicated for each protocol
  • C) Only a new Driving Adapter needs to be written; the core is untouched
  • D) The Driven Ports must be changed to support multiple protocols

Answer: C
This is one of Hexagonal Architecture's greatest strengths. The PlaceOrderUseCase Driving Port already exists. The PlaceOrderService already implements it. You simply write a new OrderGrpcController Driving Adapter that translates gRPC requests into PlaceOrderCommand and calls the existing use case. Zero changes to the domain or application core.


Q24. What is the correct location for the OrderRepository interface in a Hexagonal Architecture project?

  • A) infrastructure/persistence/ — alongside the JPA implementation
  • B) domain/model/order/ or application/port/out/ — inside the application core
  • C) ui/rest/ — near the controller that uses it
  • D) It should not be an interface — use the concrete JPA class directly

Answer: B
The OrderRepository interface (Driven Port) belongs inside the application core — either in domain/ or application/port/out/. The interface is defined BY the core and represents what the core NEEDS. The JPA implementation belongs in infrastructure/. If you put the interface in infrastructure, you've reversed the dependency rule.


Q25. An application service calls orderRepository.save(order). In Hexagonal Architecture, what actually runs at this line?

  • A) Nothing — repositories are only called at test time
  • B) The Driving Adapter translates the call and saves to the database
  • C) The Driven Adapter (e.g., JpaOrderRepository) executes the save operation
  • D) The Domain Service validates the order before saving

Answer: C
The orderRepository variable holds an instance of a Driven Adapter (e.g., JpaOrderRepositoryAdapter) that implements the OrderRepository Driven Port interface. When save() is called, the JPA adapter executes the database INSERT. The application service only knows about the interface — it is unaware whether the implementation uses JPA, MongoDB, or a file system.


Q26. Why should the domain model class (Order.java) NOT be annotated with @Entity?

  • A) JPA requires separate entity classes for performance reasons
  • B) @Entity annotation causes compilation errors in domain classes
  • C) Putting @Entity in the domain creates a framework dependency, breaking isolation
  • D) The @Entity annotation is deprecated in modern Spring

Answer: C
Putting @Entity in the domain layer creates a compile-time dependency on JPA/Hibernate in your domain. This means: you can't test the domain without the JPA library, you can't switch to MongoDB without touching domain classes, and JPA concerns (like lazy loading) start leaking into business logic. The solution is a separate OrderJpaEntity in the infrastructure layer.


Q27. What does having multiple Driving Adapters for the same Driving Port demonstrate?

  • A) A design flaw — ports should have exactly one adapter
  • B) The open/closed principle — the core is open for extension via new adapters
  • C) That the domain model has been duplicated
  • D) That the application violates the single responsibility principle

Answer: B
Multiple Driving Adapters for one Port is a feature, not a bug. It demonstrates the Open/Closed Principle: the application core is closed for modification but open for extension. You can add REST, gRPC, GraphQL, and CLI adapters all calling the same use case without modifying the core. Each new adapter extends the system without changing what already works.


Q28. Which component acts as an Anti-Corruption Layer (ACL) in Hexagonal Architecture?

  • A) The Domain Service that contains business rules
  • B) The Driving Port that defines use cases
  • C) The Driven Adapter that translates between external API models and domain models
  • D) The Application Service that orchestrates use cases

Answer: C
The Driven Adapter acts as the ACL. When your application needs to call Razorpay (payment gateway), the RazorpayPaymentGatewayAdapter translates your domain's PaymentDetails into Razorpay's ChargeRequest format, calls their API, and translates their ChargeResponse back into your PaymentResult. Your domain never knows Razorpay exists.


Q29. What is the significance of application/port/in/ vs application/port/out/ directory structure?

  • A) in/ is for test code, out/ is for production code
  • B) in/ contains inbound use case interfaces; out/ contains outbound dependency interfaces
  • C) in/ is for read operations; out/ is for write operations
  • D) in/ is for synchronous calls; out/ is for asynchronous messaging

Answer: B
port/in/ (Driving Ports) contains interfaces for what your application exposes to the outside world — use cases like PlaceOrderUseCase. port/out/ (Driven Ports) contains interfaces for what your application needs from the outside world — like OrderRepository and PaymentGateway. The naming reflects direction of dependency and data flow.


Q30. You want to enforce Hexagonal Architecture boundaries automatically in code. Which tool would you use?

  • A) Checkstyle for code style enforcement
  • B) ArchUnit for architecture rules as code tests
  • C) SonarQube for code quality metrics
  • D) Jacoco for test coverage measurement

Answer: B
ArchUnit is a Java library that lets you write architecture rules as unit tests. For example: noClasses().that().resideInAPackage("..domain..").should().dependOnClassesThat().resideInAPackage("..infrastructure.."). These tests fail the build if anyone accidentally imports infrastructure code into the domain layer.


SECTION 3 — DDD + Hexagonal Combined (Q31–Q42)


Q31. In a DDD + Hexagonal Architecture, where does the DeliveryFeePolicy (Domain Service) belong?

  • A) infrastructure/adapter/out/ — it connects to an external pricing API
  • B) domain/service/ — it contains pure business logic
  • C) application/port/in/ — it defines a use case interface
  • D) application/service/ — it orchestrates multiple domain objects

Answer: B
A Domain Service contains business logic that doesn't fit on a single entity. It belongs in the domain/service/ package inside the application core. It uses only domain objects (Order, Customer, Restaurant) and has zero framework or infrastructure dependencies. It is not an Application Service (which orchestrates) or an adapter (which connects to infrastructure).


Q32. An Application Service needs to send an email after placing an order. How should this be implemented in Hexagonal Architecture?

  • A) Directly instantiate JavaMailSender inside the Application Service method
  • B) Import javax.mail and send the email inline in the domain logic
  • C) Call notificationSender.sendOrderConfirmation(customerId, order) where NotificationSender is a Driven Port interface
  • D) Create a REST endpoint that the Application Service calls to trigger email sending

Answer: C
The Application Service calls the NotificationSender Driven Port (an interface). The actual email sending is handled by a Driven Adapter (e.g., SendGridNotificationAdapter) that implements NotificationSender. This keeps the Application Service unaware of SendGrid, SMTP, or any email technology. You can switch email providers by writing a new adapter.


Q33. Which statement best describes the relationship between a Bounded Context and a Hexagonal module?

  • A) A Bounded Context spans multiple Hexagonal modules
  • B) A Hexagonal module serves multiple Bounded Contexts
  • C) One Bounded Context typically maps to one Hexagonal module or microservice
  • D) Bounded Contexts and Hexagonal modules are unrelated concepts

Answer: C
In practice, one Bounded Context maps to one Hexagonal module (or microservice in a microservices architecture). The Bounded Context defines WHAT domain objects exist and what they mean (via Ubiquitous Language). The Hexagonal structure defines HOW to organise those objects and protect them from infrastructure. Together they form a self-contained, independently deployable unit.


Q34. When an Order is saved and its Domain Events are published to Kafka, in which layer does the Kafka publishing code live?

  • A) domain/model/order/ — the Order class publishes directly to Kafka
  • B) application/service/ — the Application Service has Kafka template injection
  • C) infrastructure/adapter/out/messaging/ — a Driven Adapter implements DomainEventPublisher
  • D) infrastructure/adapter/in/messaging/ — a Driving Adapter handles publishing

Answer: C
The Kafka publishing code lives in a Driven Adapter (KafkaDomainEventPublisher) in infrastructure/adapter/out/messaging/. It implements the DomainEventPublisher Driven Port interface. The Application Service calls eventPublisher.publish(events) through the interface — it never knows about Kafka. The domain never knows either.


Q35. A colleague puts @Entity, @Table, and @Column annotations directly on the Order domain class. What is the problem?

  • A) No problem — this is the recommended approach for simplicity
  • B) It creates a framework dependency in the domain, breaking isolation and testability
  • C) JPA annotations are not supported on classes with business methods
  • D) The @Entity annotation prevents the Order class from being used as a Value Object

Answer: B
Putting JPA annotations on the domain class creates a compile-time dependency on JPA/Hibernate in the domain layer. This violates the Dependency Rule. Now you cannot test Order without JPA, cannot switch to MongoDB without modifying Order, and JPA concerns (lazy loading, proxy classes) start interfering with business logic. The correct approach is a separate OrderJpaEntity in the infrastructure layer.


Q36. Which pattern do you use when calling an external REST API whose data model is messy and doesn't match your domain?

  • A) Open Host Service
  • B) Shared Kernel
  • C) Anti-Corruption Layer implemented as a Driven Adapter
  • D) Conformist

Answer: C
The Anti-Corruption Layer (ACL) protects your clean domain model from a messy external model. In Hexagonal Architecture, the ACL is implemented as a Driven Adapter. It translates between the external API's model and your domain model. Your domain never sees the external API's types — only your own clean domain types.


Q37. In testing a PlaceOrderService, which dependencies should be replaced with test doubles?

  • A) The Order domain object and Money value object
  • B) The DeliveryFeePolicy domain service
  • C) The OrderRepository, CustomerRepository, and DomainEventPublisher driven ports
  • D) Nothing — always test with the real database

Answer: C
In a unit test of PlaceOrderService, you replace the Driven Port implementations (repositories, event publisher, external gateways) with test doubles (in-memory implementations or mocks). You do NOT replace the Domain objects (Order, Money) or Domain Services (DeliveryFeePolicy) — those are part of the business logic you are actually testing.


Q38. What is the correct sequence when PlaceOrderService executes?

  • A) Publish events → Save to DB → Call domain logic → Load domain objects
  • B) Load domain objects → Call domain logic → Save to DB → Publish events
  • C) Call domain logic → Load domain objects → Publish events → Save to DB
  • D) Save to DB → Publish events → Load domain objects → Call domain logic

Answer: B
The correct sequence is: (1) Load domain objects from repositories, (2) Call domain logic (create/modify aggregates, apply domain services), (3) Save changes to the repository, (4) Publish Domain Events. Events are published last — after saving — to ensure the data is persisted before other services react to the event.


Q39. You have Order (Aggregate Root) with OrderItem entities inside. Which repository should exist?

  • A) Both OrderRepository and OrderItemRepository
  • B) Only OrderRepository
  • C) Only OrderItemRepository
  • D) A FoodRepository that wraps both

Answer: B
There should be ONE repository per Aggregate Root — only OrderRepository. You never create a repository for entities that are owned by an Aggregate (like OrderItem). To access or modify OrderItem, you load the Order aggregate and call methods on it. The Order root then manages its items. This preserves the consistency boundary.


Q40. What does the @Transactional annotation in PlaceOrderService ensure in the context of DDD + Hexagonal?

  • A) It caches the order in Redis for faster access
  • B) The entire use case (load, domain logic, save) is one atomic database operation
  • C) It automatically publishes Domain Events to Kafka
  • D) It converts the Application Service into a Domain Service

Answer: B
@Transactional ensures that the entire PlaceOrderService.execute() method — loading customer, creating the order, saving it — happens in one database transaction. If saving fails, the entire operation rolls back. Note: Domain Events should be published AFTER the transaction commits (not inside it) to avoid publishing events for rolled-back transactions.


Q41. What is the correct place for input validation (checking that an order ID is not null) in Hexagonal Architecture?

  • A) Only in the domain Order entity
  • B) Only in the database constraint
  • C) Format/type validation in the Driving Adapter; business rule validation in the domain
  • D) In a global Spring @ExceptionHandler class

Answer: C
Validation is layered: (1) Driving Adapters (e.g., REST controller) validate format and type — is the field present? Is it a valid UUID format? (2) Value Objects self-validate in their constructor — new OrderId(null) throws. (3) Aggregates validate business rules — order.cancel() checks if the status allows cancellation. Database constraints are a last line of defence, not the primary validation.


Q42. A Money value object is created with a negative amount. What should happen?

  • A) The application should silently convert it to a positive value
  • B) The Money constructor should throw an IllegalArgumentException immediately
  • C) A validation service should be called to check the value later
  • D) The value should be stored and validated only when payment is processed

Answer: B
Value Objects are self-validating. The Money constructor should immediately throw an IllegalArgumentException (or a domain-specific exception) if the amount is negative. This is "fail fast" design — an invalid Money object can never exist. If validation is deferred (Options C and D), invalid objects can propagate through the system creating harder-to-debug problems.


SECTION 4 — Advanced Scenarios (Q43–Q50)


Q43. The Outbox Pattern is used with Domain Events. What problem does it solve?

  • A) It reduces the size of domain event payloads
  • B) It ensures events are published reliably even if the application crashes after saving to the database
  • C) It converts synchronous REST calls to asynchronous messages
  • D) It prevents duplicate domain events from being created in the aggregate

Answer: B
Without the Outbox Pattern: save to database succeeds → application crashes → event never published → payment service never charges, restaurant never notified. The Outbox Pattern saves the event to an outbox table in the same database transaction as the aggregate. A separate process then reads the outbox and publishes to Kafka. If the app crashes, the event is still in the database and will be published when the app restarts.


Q44. Which of the following is a sign that your Aggregate is too large?

  • A) The Aggregate has more than two child Entities
  • B) Every transaction that modifies the Aggregate locks a huge amount of data, causing contention
  • C) The Aggregate Root has more than five methods
  • D) The Aggregate uses more than one Value Object

Answer: B
A God Aggregate that contains too many entities causes database locking contention — every operation on the aggregate locks all its data. This reduces concurrency and throughput. The fix is to split the aggregate and use Domain Events for cross-aggregate consistency. The number of entities or methods (A, C) doesn't indicate a problem by itself.


Q45. CQRS (Command Query Responsibility Segregation) in a DDD context means:

  • A) Commands use REST, queries use GraphQL
  • B) Commands modify state through Aggregates; queries use separate read models optimised for display
  • C) Commands are synchronous; queries are asynchronous
  • D) Commands are stored in SQL; queries use NoSQL

Answer: B
CQRS separates the write model from the read model. Commands (PlaceOrder, CancelOrder) go through the full DDD stack: Application Service → Aggregate → Repository → database. Queries (GetOrderDetails, ListOrders) bypass the Aggregate entirely and use read-optimised models — flat DTOs from efficient queries or dedicated read tables. This avoids loading full aggregates just for display.


Q46. Two services need to maintain consistency across a workflow: Order is placed → Payment is taken → Restaurant is notified. What is the recommended DDD pattern?

  • A) One distributed XA transaction across all three services
  • B) The Saga pattern using Domain Events and compensating transactions
  • C) A shared database where all three services write to the same tables
  • D) Making all three service calls synchronous from the Order service

Answer: B
The Saga pattern handles long-running processes across multiple bounded contexts. Each step is one local transaction. Completion of each step publishes a Domain Event. The next step reacts. If a step fails, compensating transactions roll back earlier steps (e.g., if payment fails, cancel the order). This gives you eventual consistency without distributed transactions, which are complex and fragile.


Q47. A team argues whether to put order cancellation rules in OrderService.cancelOrder() (service class) or Order.cancel() (entity method). What does DDD recommend?

  • A) Put it in OrderService because services are easier to test
  • B) Put it in Order.cancel() because business rules belong in the entity that owns the state
  • C) Put it in the REST controller for quick access
  • D) Put it in the database stored procedure for performance

Answer: B
Business rules belong in the entity that owns and manages the relevant state. The cancellation rules ("cannot cancel if shipped") are about the Order's state — so they belong in Order.cancel(). This is the opposite of the Anemic Domain Model anti-pattern. Putting rules in a service (A) creates scattered, duplicated logic; in a controller (C) is a clear violation; in a stored procedure (D) creates invisible, hard-to-test logic.


Q48. A junior developer proposes: "Let's just use one big OrderService class with 50 methods for all order operations. It's simpler." What is the main problem with this approach?

  • A) One service class is a performance bottleneck
  • B) Java doesn't support classes with more than 30 methods
  • C) It creates a God Class — a single class with too many responsibilities, making it hard to maintain, test, and understand
  • D) Spring does not support services with more than 20 methods

Answer: C
A God Class (or God Object) is a well-known anti-pattern. A 50-method OrderService would contain: business rules, database queries, email sending, event publishing, validation, orchestration — all mixed together. No single developer can understand it fully. Changes in one part break another. Tests become enormous. The DDD solution is to split responsibilities: business rules in entities, orchestration in Application Services, infrastructure in adapters.


Q49. What is the purpose of the OrderPersistenceMapper class in a project using DDD + Hexagonal Architecture?

  • A) It maps REST request bodies to Application Service commands
  • B) It converts between the domain Order object and the OrderJpaEntity persistence model
  • C) It maps Domain Events to Kafka message payloads
  • D) It converts between Money and BigDecimal for calculations

Answer: B
The OrderPersistenceMapper is a Driven Adapter utility that translates between the two separate models: the domain Order (pure business model) and the OrderJpaEntity (JPA/database model). This translation allows both models to evolve independently — you can change the domain model without changing the database schema, and vice versa. It lives in infrastructure/adapter/out/persistence/.


Q50. Which combination represents the CORRECT implementation of Hexagonal Architecture?

  • A) Domain layer imports @Entity; Application Service imports KafkaTemplate; Repository interface in Infrastructure
  • B) Domain layer is pure Java; Application Service implements Driving Port; Repository interface in Application Core; Adapters in Infrastructure
  • C) Domain layer imports Spring @Component; Repository interface in Domain; Adapters in Application layer
  • D) Domain layer contains REST controllers; Application Service contains database queries; Infrastructure contains business rules

Answer: B
The correct setup is:

  • Domain layer: Pure Java, zero framework imports, contains Entities, Value Objects, Aggregates, Domain Events, Domain Services
  • Application layer: Application Services implement Driving Ports; Driven Port interfaces (OrderRepository, PaymentGateway) defined here
  • Infrastructure layer: All Driving Adapters (REST, Kafka consumer) and Driven Adapters (JPA, Kafka producer, external APIs) live here

Option A violates the Dependency Rule. Option C puts adapters in the wrong layer. Option D completely inverts every responsibility.


Quick Reference — Answer Key

Q Ans Q Ans Q Ans Q Ans Q Ans
1 B 11 C 21 B 31 B 41 C
2 C 12 B 22 C 32 C 42 B
3 B 13 B 23 C 33 C 43 B
4 B 14 B 24 B 34 C 44 B
5 C 15 B 25 C 35 B 45 B
6 C 16 C 26 C 36 C 46 B
7 B 17 B 27 B 37 C 47 B
8 C 18 B 28 C 38 B 48 C
9 B 19 C 29 B 39 B 49 B
10 C 20 C 30 B 40 B 50 B

Topic-wise Score Guide

Score Interpretation
45–50 Excellent — production-ready understanding
38–44 Good — solid foundation, review weak areas
28–37 Fair — re-read explanations for wrong answers
Below 28 Revisit the notes from the beginning

Focus areas if you scored low:

  • Q1–Q15: Re-read Part A (DDD Fundamentals)
  • Q16–Q30: Re-read Part B (Hexagonal Architecture)
  • Q31–Q42: Re-read Part C (DDD + Hexagonal Together)
  • Q43–Q50: Re-read Part C advanced sections and Context Mapping

End of Notes. Good luck with your learning and interviews!
The most important lesson: DDD is not about patterns — it is about understanding the business deeply and letting that understanding drive every design decision.