Over the last 11 years, I have enjoyed and built-up my expertise in software development working with Java technologies. Since late 2016, I have been working more & more with Golang.

While working in Go as an everyday-language, I noticed that DDD doesn’t resonate within the Golang Community because most people think it influences an Object Oriented Programming approach when writing Go code since the majority of the material available to read is written around Object-Oriented Design.

This article will focus on explaining the related concepts of DDD in Golang, but the author's understanding of DDD based on his own research.

What is DDD (Domain-Driven Design)?

The idea of domain-driven design was introduced in 2003 by Eric Evans in Tackling Complexity in the Heart of Software .

Simple definition of domain-driven design in Go context is: The idea of solving valid business problems of the organization in the optimal way through code. Implement the solution in a way that your business users will understand without any extra translation from technical language.

The Two Sides Of Domain-Driven Design

Domain-Driven Design is composed of two different sides, the strategic and the tactical.
Strategic Design
The strategic design is the one in which you and the domain experts analyze a domain, define its ubiquitous language, bounded contexts, and look for the best way to let them communicate. As we can easily assume, the strategic design is programming language agnostic. This is also one of the reasons, I think Go is an good fit for the methodology.
Tactical Design
Compared to strategic domain-driven design, tactical design is much more hands-on and closer to the actual code. Strategic design deals with abstract wholes whereas tactical design deals with classes and modules. The purpose of tactical design is to refine the domain model to a stage where it can be converted into working code.

Domain

Domain includes the ideas, knowledge, data, metrics, and goals that revolve around business problem we are trying to solve. It contains all the rules and patterns that will help us deal with complex business logic. Moreover, they will be useful to meet the requirements of our business.
I genuinely believe that every software system we ever built has a model at its heart.
If this model matches the underlying domain well, the software will accept enhancements more easily, and it has a much better chance of surviving and thriving intact for years. The model may not be obvious; it may exist only in the mind of the developers, but it exists nonetheless.
Subdomains
The domain concept is very broad and abstract. To make it more concrete and tangible, it makes sense to split it up into smaller parts called subdomains. Finding these subdomains is not always an easy thing to do, and if we get them wrong, we can run into trouble down the road when the pieces in our puzzle all of a sudden do not fit well together. Subdomains can be further categorized into Core, Supporting, and Generic
Core Subdomain
An organization cannot succeed (or even exist) without being exceptionally good in their core domain. Because the core domain is so important, it should receive the highest priority, the biggest effort and the best developers.
Supporting Subdomain
This is necessary for the organization to succeed, but it neither fall into the core nor a generic domain category, because it still requires some level of specialization for the organization.
Generic Subdomain
This does not contain anything special to the organization but is still needed for the overall solution to work. We can save a lot of time and work by trying to use off-the-shelf software for generic subdomains.

The Ubiquitous Language

The Ubiquitous Language is a methodology that refers to the same language domain experts and developers use when they talk about the domain they are working on. At the time of design, we should think in terms of behavior and types instead of structs and functions. So that any non technical person can read and understand business without any training. This is necessary because projects can face serious issues with a disrupted language. The subdomains are also a part of the ubiquitous language, and we may even need to define different dialects of the language for different subdomains.
Codebase should reflect business logic

Bounded Context

Bounded context is a central pattern in domain-driven design that contains the complexity of the application. Bounded contexts actually represent boundaries in which a certain subdomain is defined and applicable.
In Golang, Packaging have a key role when writing Go code, and we have to take the full power of the package mechanism the language delivers and relies on.
In other words, we need to apply divide and conquer and split domain up into smaller, more or less independent models with clearly defined boundaries. Every bounded context has its own name and this name is a part of the ubiquitous language.

Context Mapping

Design a package as a bounded context and treat the communication across packages as such using strategic patterns to identify the relationships and the contracts.
A way to define bounded contexts & their relationships
Symmetric Relationship
Two software artifacts or systems in two bounded contexts are considered to be in symmetric relationship if they are mutually dependent or do not influence success/failure in other bounded context. Switching sides of a bounded context do not change there meaning.
Mutually dependent or Zero dependency on other bounded contexts
Shared Kernel
The teams share a subset of the domain model including artifacts (i.e. Code, binary, database etc.). These teams should form a partnership.
Partnership
The teams should have a mutual agreement on process for coordination, development plan, and delivery.
Separate Ways
There is no connection between the bounded contexts. The teams can find their own solutions in their domains.
Big Ball Of Mud
A (part of a) system which is a mess by having mixed models & inconsistent boundaries. Don’t let this lousy model propagate into the other Bounded Contexts.
A little copying is better than litter dependency
Upstream / Downstream Relationship
Any update on an upstream system will influence the downstream counterpart, where as any change in downstream system may or may not influence upstream system.
Highly dependent on upstream system bounded context
Customer-Supplier
Both upstream and downstream team come together to define a contract between upstream and downstream context. Being a customer the downstream team gains some influence in defining contract and priorities for upstream system. If we define this correctly upstream team can adhere to this and evolve independently without fear of breaking anything in downstream system.
Open Host Service
A bounded context that offers a defined set of functionalities exposed to other systems. Any downstream system can implement their own integration.
Published Language
A well documented shared language published by an upstream context, which is eventually used as a common language between bounded contexts.
Conformist
The downstream context conforms to the model of the upstream and there is no translation of models between them.
Tight coupling between two context and should be avoided, if possible
Anti-corruption Layer
A layer that isolates/abstracts the down stream's models from another system’s models by translation. It can also be considered as a component used to translate between different bounded contexts, preventing one context from negatively affecting another and maintaining the integrity of each bounded context’s model.
Make use of integration layer (or Adapter) to remove tight coupling between contexts.

Benefits and Limitations

Domain-driven design brings significant benefits in terms of improved maintainability, reduced complexity, consistency and integrity, and more., but as we all know nothing comes for free, Domain-driven design comes up with its own tradeoffs and considerations. It is important to balance its remarkable benefits and the potential complexities and challenges it may introduce.
The Significant Benefits
  • Improved Maintainability - DDD is well-suited for environments with intricate business rules and complex domains. It aims to abstract these complexities into more manageable segments.
  • Unified Understanding Teams - DDD promotes a unified understanding across cross-functional teams, helping to align team members with the business's goals.
  • Accommodate Adaptations - Organizations undergoing rapid changes, DDD provides a framework that can accommodate adaptations, reducing the potential for extensive reworks.
  • Ensure Alignment - DDD ensures alignment between software development and business domains.
  • Improved Flexibility - DDD encourages flexibility providing a foundation that can be more amenable to changes, aligning with the evolving business requirements.
  • Improved Collaboration - DDD helps reduce knowledge gaps between developer and domain experts, improving collaboration.
  • Structures Complex Systems - DDD structures complex systems around the business domain, using a model that evolves over time through collaborative efforts between domain experts and software developers.
The Main Trade-Offs
  • Significant Initial Overhead - It involves significant initial overhead, as defining accurate contexts and detailed models can delay development start. In saying that, the long-term benefit of simpler complexity management is the major gain from this price to pay.
  • Steep Learning Curve - It adds a layer of complexity as It requires a steep learning curve & rigorous discipline.
  • Misalignment Risk - Without ongoing collaboration with domain experts, domain-driven design risks misalignment, leading to solutions that drift from business realities and prove less effective.
  • Consistency Challenges - It can be tough need extra work to make sure that the domain driven design is being followed in religiously.

Final Thought

Domain-driven design fosters collaboration between developers and domain experts, helping to design software that closely aligns with business needs. It doesn't fit every scenario, but when it does, the impact is clear. Each project's unique demands dictate its suitability. It is an excellent and popular choice for large, complex environments where its structured approach can significantly aid in managing intricate business rules and interactions.
In Golang, a package represent boundaries in the given context along with specialized meaning.