Backend Development 21 min read

Applying Domain‑Driven Design and Hexagonal Architecture to Build Scalable Microservice Systems

The article explains how business complexity and maintenance pain points can be mitigated by adopting Domain‑Driven Design, strategic and tactical patterns, hexagonal architecture, and a well‑structured microservice framework—illustrated with Go code examples and practical migration steps for large‑scale systems.

Architect
Architect
Architect
Applying Domain‑Driven Design and Hexagonal Architecture to Build Scalable Microservice Systems

The article begins by describing common pain points in growing businesses: heavy reliance on a few domain experts, difficulty understanding legacy code, painful project handovers, and escalating complexity as features accumulate.

It then classifies three complexity growth patterns—coupling, chimney (vertical silo), and reuse—and argues that a reuse‑oriented approach, powered by Domain‑Driven Design (DDD), can keep complexity growth sub‑linear.

DDD’s strategic design is used to split the system into sub‑domains and bounded contexts, while tactical design (layered and hexagonal architecture) defines clear responsibilities for gateway, service, and data‑access layers. The article shows how this architecture enables plug‑able interfaces, isolates business logic, and supports both synchronous (interface) and asynchronous (job) services.

Key design decisions include:

Adopting a six‑layer hexagonal structure combined with DDD.

Using CQRS for read/write separation where appropriate.

Encouraging a shared common package for constants and utilities, while keeping domain‑specific code isolated.

Code examples illustrate the entity, mapper, and service definitions in Go. For instance, the Activity entity is defined as:

type Activity struct {
    ID              int64
    Name            string
    ClassifyId      int32 // resource template id
    State           ActivityState
    Type            DimensionType
    Dimensions      []*ActivityDimension
    DynamicResource resource.DynamicResource
    Ctime           common.Time
    Mtime           common.Time
}

func (act *Activity) Validate() error {
    for _, d := range act.Dimensions {
        if act.ID != d.ActivityId || act.State != d.State || act.Type != d.Type {
            return ecode.Error(ecode.ParamInvalid, "dimension and activity fields mismatch")
        }
    }
    return act.DynamicResource.Validate()
}

The mapper converting a protobuf DTO to the domain object is shown as:

func ActivityFromDTO(dto *pb.DmActivity) (*entity.Activity, error) {
    dynamicResource, err := parseResource(dto.ClassifyId, dto.GetResource())
    if err != nil {
        return nil, err
    }
    act := &entity.Activity{
        ID:              dto.GetId(),
        Name:            dto.GetName(),
        ClassifyId:      dto.GetClassifyId(),
        State:           entity.ActivityState(dto.GetState()),
        Type:            entity.DimensionType(dto.GetType()),
        Dimensions:      ActivityDimensionListFromDTO(dto.GetDimension()),
        DynamicResource: dynamicResource,
        Ctime:           dto.GetCtime(),
        Mtime:           dto.GetMtime(),
    }
    if err = act.Validate(); err != nil {
        return nil, err
    }
    return act, nil
}

The article also outlines the migration from a fragmented legacy architecture (separate live‑stream and on‑demand bullet‑screen services) to a unified platform, describing gateway, business, and component layers, and showing before‑and‑after architecture diagrams.

Applicability analysis lists scenarios where DDD is beneficial—complex domains, multi‑team collaboration, continuous iteration—and where it is not—stable, low‑complexity services or performance‑focused problems.

Benefits include reduced duplication, clearer boundaries, easier database migration, and better team alignment; costs involve extra DTO/DO/PO mapping, higher design effort, and the need for skilled developers.

Finally, the article discusses future directions such as automated mapper generation with LLMs, scaling the architecture across the organization, and the trade‑offs of further splitting domain services for low‑traffic B‑side services.

backendmicroservicesGoDDDsoftware designhexagonal architectureKratos
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

login 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.