Overview

5 Domain primitives

This chapter advocates building security into the domain model by introducing domain primitives—small, immutable, precise value types that enforce their invariants at creation. Rather than relying on scattered validation and ad hoc checks, the model itself becomes the first line of defense: every method argument, return value, and stored field can be expressed through domain primitives that are always valid by construction. The approach reduces ambiguity, lowers cognitive load, hardens programmatic APIs, and provides a foundation for defense in depth, while also outlining techniques to prevent data leaks and relating the approach to concepts from taint analysis.

Domain primitives refine Domain-Driven Design value objects by requiring explicit invariants and forbidding use of raw language primitives for meaningful concepts. They capture both rules and behavior close to the data (for example, a constrained Quantity with safe operations), and their meaning is defined within the current bounded context; well-known external terms (like ISBN) should retain their external semantics, while overloaded terms should be split or renamed. A reusable library of such primitives lets teams harden APIs (e.g., using an InternalAddress rather than a generic IP address), avoids exposing internal domain types in public interfaces through DTO translation, and declutters entities so they can focus on semantic decisions instead of repetitive validation. This consistently pushes early-stage validation to the edges, keeps entity code smaller and safer, and is practical in both greenfield and incremental legacy improvements with negligible performance costs for typical business systems.

To mitigate leaks of sensitive data, the chapter introduces read-once objects—often domain primitives—that allow a sensitive value to be retrieved only once, resist serialization, and avoid accidental exposure (e.g., passwords modeled as mutable char arrays that are wiped after access). These patterns help detect unintentional use during development and guard against operational pitfalls such as session persistence. Finally, the chapter connects these ideas to taint analysis: marking inputs as suspicious (sources), clearing them through checks (untainting), tracking how taint propagates, and stopping unsafe uses (sinks). While runtime taint analysis is still research-heavy and costly, designing with domain primitives and read-once objects achieves many of the same security benefits through robust, explicit modeling.

Figure 5.1. The meaning of a term is defined within the boundaries of a given context.
ch05 dp redefining terms small
Figure 5.2. Using an externally defined term without changing its meaning
ch05 dp using external terms small
Figure 5.3. Introducing new terms instead of redefining existing ones
ch05 dp avoid redefining terms small
Figure 5.4. The lifecycle of a read-once object
ch05 read once object password small
Figure 5.5. Remodeling the User object, causing sensitive data to leak
ch05 read once object remodeling small
Figure 5.6. Tainted data stopped, cleaned data let through.
ch05 taint small

 Summary

  • Domain primitives are the smallest building blocks and form the basis of your domain model.
  • You should never represent a concept in your domain model as a language primitive or a generic type.
  • If a term in your domain already exists outside of your domain but with a slightly different meaning, you should introduce a new term instead of redefining the existing one.
  • A domain primitive is immutable and can only exist if it’s valid in the current domain.
  • When domain primitives are used, the rest of the code is greatly simplified and becomes more secure.
  • You should harden APIs by using your domain primitive library.
  • A read-once object is a useful way to represent sensitive data in your code.
  • The value of a read-once object can only be retrieved once.
  • The read-once object design pattern can mitigate leakage of sensitive data.
  • Domain primitives provide the same type of security that concurrent taint analysis would do.

FAQ

What is a domain primitive and how does it differ from a traditional value object?A domain primitive is an immutable, precisely defined type that represents the smallest meaningful concept in a domain and enforces its invariants at creation. If a domain primitive exists, it is valid by definition. Unlike “plain” value objects, domain primitives mandate invariants, prohibit null and generic representations of domain concepts, and may encapsulate domain operations to preserve rules.
What role do invariants play in domain primitives, and when are they enforced?Invariants define exactly what makes a concept valid (for example, Quantity must be 1–200). They are enforced at construction time, so invalid instances cannot be created. This fail-fast approach removes revalidation boilerplate, reduces ambiguity, and prevents illegal states from entering the system.
Why avoid language primitives and generic types for domain concepts?Primitives and generic types (such as int, String, or null) are ambiguous, error-prone, and allow invalid states. They carry no domain meaning, are easy to misuse, and tend to spread validation logic everywhere. Domain primitives encode meaning, uphold invariants centrally, and lower cognitive load and defect rates.
How do context boundaries affect the definition of a domain primitive?The meaning of a domain primitive is defined within the current domain’s context. Sometimes you tailor definitions (for example, a constrained email local-part), while other times you must keep external definitions intact (for example, ISBN). If a well-known term collides with domain needs, introduce new terms (for example, BookId containing either ISBN or UnpublishedBookNumber) to avoid ambiguity and security bugs.
How do I build and use a domain primitive library?Model core concepts as domain primitives and collect them into a reusable library. Use them pervasively as method parameters, return values, constructor arguments, and entity fields. Because these types are valid by construction, you can pass them safely throughout your codebase, reduce revalidation, and speak the ubiquitous language consistently.
How do domain primitives harden programmatic APIs?Type API inputs/outputs with domain primitives so every method boundary gets built-in validation. For example, accept an InternalAddress instead of InetAddress when sending audit logs, ensuring only vetted internal destinations are allowed. The remaining checks often reduce to null checks; invalid inputs can’t compile or be constructed.
Should I expose domain primitives in public APIs?No. Avoid coupling your internal domain to external consumers. Use DTOs at the public boundary with protocol-relevant constraints, then translate DTOs to domain primitives inside the API. This preserves security, prevents leaking sensitive rules, and allows the API and domain to evolve independently.
What is a read-once object and how does it prevent data leaks?A read-once object wraps sensitive data (such as passwords or SSNs) so its value can be retrieved exactly once; subsequent reads fail. It also blocks serialization and hides values in toString. This design makes unintended reuse and accidental leakage detectable early (often during testing) and reduces exposure in memory and logs.
How do domain primitives simplify entities and improve security?- They offload lexical/syntax/size validation from entities, letting entities focus on semantic checks and state transitions. - They make methods shorter and clearer, reducing missed edge cases (such as negative quantities). - They enforce consistency system-wide. Performance overhead is typically negligible with modern GC and dwarfed by I/O; you’d only reconsider in extreme, highly constrained scenarios.
What is taint analysis and how does it relate to domain primitives?Taint analysis marks inputs as “tainted” and tracks them at runtime from sources to sinks, clearing taint when adequate checks occur. Constructors are treated as key validation points. Systems built on domain primitives (which validate in constructors and operations) align well with taint analysis, though runtime tainting remains too costly for production in most cases.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Secure by Design ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Secure by Design ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Secure by Design ebook for free