How DDD Transforms a Complex Pricing System: A Step‑by‑Step Architectural Guide

This article walks through the practical application of Domain‑Driven Design to a large‑scale pricing system, detailing business understanding, strategic and tactical design, context integration, layered architecture, and concrete Java implementations that illustrate how DDD improves modularity, testability, and maintainability.

Architect
Architect
Architect
How DDD Transforms a Complex Pricing System: A Step‑by‑Step Architectural Guide

Business Understanding

The pricing system (valuation engine) processes an inspection report to produce a price for various recycling and retail scenarios (online C2B recycle, store recycle, B2C marketplace, etc.). The simplified workflow is:

Parse the inspection report.

Convert the parsed report into valuation items.

Select a quotation process based on request parameters.

Execute the configured quotation method to obtain the final price.

Because pricing rules differ across scenarios, a Scenario concept is introduced to bind configuration parameters and quotation methods.

The inspection report contains detailed, professional data (condition, grade, etc.). Directly exposing this data to operators would be costly, so the system converts report items into maintenance‑friendly valuation items.

Strategic Design

Based on the business analysis, the system is divided into five sub‑domains:

Scenario (generic) – provides configuration parameters for other domains.

Inspection‑report parsing (support) – supplies upstream data for valuation.

Valuation‑item conversion (support) – also supplies upstream data.

Quotation‑process (support) – encapsulates the workflow.

Quotation‑method (core) – implements the actual pricing algorithms.

Tactical Design

Each sub‑domain becomes a bounded context. For example, the inspection‑report parsing context uses an anti‑corruption layer to adapt an external inspection service. The context aggregates a single QcReport entity whose value objects are product category, brand, and model.

Context Integration

Integration relationships are modelled with a subset of DDD patterns that add the least conceptual overhead for this project:

Collaborator – expresses a direct dependency between two bounded contexts.

Open Host Service / Published Language – defines a stable interface for external systems.

Anti‑Corruption Layer – isolates the internal model from foreign models.

Arrows in the diagram indicate upstream (U) and downstream (D) directions; the ACL sits between the internal contexts and the external host service.

Architecture Design

Considering team experience and learning cost, a loosely‑coupled layered architecture is chosen over hexagonal or onion styles. The layers are:

API – interface definitions consumed by other services.

Application – implements API contracts and orchestrates use cases.

Domain – contains core business logic and domain services.

Infrastructure – provides data access, external adapters, and anti‑corruption implementations.

Common utilities (constants, tools, basic algorithms) are placed in a common package, yielding the following project structure:

evaluation_sys
  ├─ api
  ├─ application
  ├─ domain
  ├─ infrastructure
  └─ common

Business Logic Implementation

In a traditional MVC three‑layer stack (controller → service → DAO), business logic tends to accumulate in the service layer, producing a “fat service” that is hard to understand and maintain. DDD separates concerns by:

Using application services to orchestrate domain services (the “use‑case” level).

Encapsulating detailed algorithms in domain services.

Giving domain objects behavior, which aligns the code with the problem domain.

Example of an application service that drives the valuation flow:

public EvaluateResult eval(Scenario scenario, EvaluateContext context) {
    // obtain inspection report
    QcReport report = qcReportService.parseReport(context.getQcCode());
    // convert to valuation items
    EvaluateItems evaluateItems = evaluateItemsService.transfer(report, scenario);
    // execute valuation process
    EvaluateResult result = evaluateProcessService.evaluate(scenario, context, evaluateItems);
    return result;
}

The corresponding domain service builds the algorithm list, creates a valuation process, and runs it:

public class EvaluateProcessService {
    public EvaluateResult evaluate(Scenario scenario, EvaluateContext context, EvaluateItems evaluateItems) {
        // get valuation algorithms for the scenario
        List<EvaluateAlgorithm> algorithms = EvaluateAlgorithmFactory.create(scenario);
        // build the valuation process (e.g., max‑price, fallback, etc.)
        EvaluateProcess process = EvaluateProcessFactory.create(scenario, context, algorithms, evaluateItems);
        // run the process
        return process.evaluate();
    }
}

A concrete process that selects the highest price among all algorithms:

public class MaxPriceEvaluateProcess implements EvaluateProcess {
    private Scenario scenario;
    private EvaluateProcess context;
    private List<EvaluateAlgorithm> algorithms;
    private EvaluateItems evaluateItems;

    public EvaluateResult evaluate() {
        long maxPrice = 0L;
        for (EvaluateAlgorithm algorithm : algorithms) {
            long price = algorithm.calculate(context, evaluateItems);
            if (price > maxPrice) {
                maxPrice = price;
            }
        }
        return new EvaluateResult(maxPrice);
    }
}

By decomposing the workflow into these layers and services, the code becomes highly readable, testable, and aligns with human problem‑solving steps, which eases onboarding and future extensions.

Conclusion

Adopting DDD introduces additional concepts and learning overhead. The trade‑off is justified only when the benefits—clear bounded contexts, reduced coupling, improved testability, and easier maintenance—outweigh the added complexity. Teams should evaluate project size, expected evolution, and team expertise before committing to a full DDD implementation.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSoftware ArchitectureMicroservicesDomain-Driven DesignClean ArchitectureDDDPricing System
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.