{"id":2995,"date":"2025-03-13T22:38:44","date_gmt":"2025-03-13T21:38:44","guid":{"rendered":"https:\/\/contentlabstudy.com\/soft\/?p=2995"},"modified":"2026-09-24T21:16:40","modified_gmt":"2026-09-24T20:16:40","slug":"domain-driven-design-principles","status":"publish","type":"post","link":"https:\/\/contentlabstudy.com\/soft\/domain-driven-design-principles\/","title":{"rendered":"Domain-Driven Design Principles"},"content":{"rendered":"<p><strong>Domain-driven design<\/strong> is a way to align the software\u2019s model with the business\u2019s language and boundaries. It is not \u201cuse entities and repositories\u201d and not microservices. If the domain is simple CRUD, a boring layered app is enough. Use DDD when rules are the product (pricing, underwriting, trading, clinical workflows).<\/p>\n<h2>Ubiquitous language<\/h2>\n<p>The same words in conversation, tests, and types. If the business says \u201cpolicy binds\u201d and the code says <code>ActivateContract()<\/code>, you will translate forever and get it wrong. Rename toward the domain. When two groups use the same English word differently, you have two languages \u2014 that is a bounded context, not a thesaurus problem.<\/p>\n<h2>Bounded contexts<\/h2>\n<p>A model is valid inside a boundary. \u201cCustomer\u201d in billing is not \u201cCustomer\u201d in support. Draw the map: names, teams, data owners, integration (shared DB is an implicit, usually bad, mapping). Context mapping (customer\/supplier, conformist, anti-corruption layer, published language) is how they talk without merging models.<\/p>\n<h2>Tactical building blocks (inside one context)<\/h2>\n<ul>\n<li><strong>Entity<\/strong> \u2014 identity over time (Order 1234).<\/li>\n<li><strong>Value object<\/strong> \u2014 equality by value, immutable (Money, Email). See <a href=\"\/soft\/c-record-methods\/\">records<\/a>.<\/li>\n<li><strong>Aggregate<\/strong> \u2014 a cluster with one root; invariants enforced on that root; persist atomically. Small aggregates. Do not make \u201cthe whole company\u201d one aggregate.<\/li>\n<li><strong>Domain event<\/strong> \u2014 something that happened in the language of the domain, not \u201crow updated.\u201d<\/li>\n<li><strong>Repository<\/strong> \u2014 collection-like access to aggregates you persist. Not a wrapper that exposes <code>IQueryable<\/code> to controllers.<\/li>\n<li><strong>Domain service<\/strong> \u2014 a rule that does not naturally sit on one entity. Keep them rare.<\/li>\n<\/ul>\n<h2>Strategic: where the value is<\/h2>\n<p><strong>Core domain<\/strong> \u2014 the differentiator; your best people. <strong>Supporting<\/strong> \u2014 necessary, not how you win. <strong>Generic<\/strong> \u2014 buy or clone (auth, email). Spending your core team on a custom CMS is how DDD projects die.<\/p>\n<h2>Example<\/h2>\n<p><code>Order.AddLine<\/code> refuses a cancelled order. The API maps that exception to 409. The test says \u201corder\u201d and \u201cline,\u201d the same words the business uses.<\/p>\n<pre><code>public sealed class Order\n{\n    public enum Status { Placed, Cancelled }\n    private readonly List&lt;OrderLine&gt; lines = new();\n    public Status State { get; private set; } = Status.Placed;\n    public IReadOnlyList&lt;OrderLine&gt; Lines =&gt; lines;\n\n    public void AddLine(string sku, int qty)\n    {\n        if (State == Status.Cancelled)\n            throw new InvalidOperationException(\"A cancelled order cannot take a line.\");\n        lines.Add(new OrderLine(sku, qty));\n    }\n}\n\npublic sealed record OrderLine(string Sku, int Qty);<\/code><\/pre>\n<h2>What breaks it<\/h2>\n<p>Public setters, and the rule lives in the controller. A batch job that touches <code>Lines<\/code> does not know about the rule. Put the check on the aggregate.<\/p>\n<pre><code>public sealed class AnemicOrder\n{\n    public bool Cancelled { get; set; }\n    public List&lt;OrderLine&gt; Lines { get; } = new();\n}\n\nstatic void AddFromController(AnemicOrder order, string sku, int qty)\n{\n    if (!order.Cancelled)\n        order.Lines.Add(new OrderLine(sku, qty));\n}<\/code><\/pre>\n<h2>Pitfalls<\/h2>\n<ul>\n<li>Folder names \u201cDomain\/Application\/Infrastructure\u201d with an anemic UserDto and all rules in the controller.<\/li>\n<li>One \u201ccanonical data model\u201d for the whole enterprise.<\/li>\n<li>Aggregates that span transactions \u201cbecause the invariant is big.\u201d<\/li>\n<\/ul>\n<p>Getting there from a mess: <a href=\"\/soft\/migrating-legacy-systems-to-ddd\/\">Migrating legacy systems to DDD<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Domain-driven design is a way to align the software\u2019s model with the business\u2019s language and boundaries. It is not \u201cuse [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[4],"tags":[],"class_list":["post-2995","post","type-post","status-publish","format-standard","hentry","category-software-design"],"_links":{"self":[{"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/posts\/2995","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/comments?post=2995"}],"version-history":[{"count":4,"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/posts\/2995\/revisions"}],"predecessor-version":[{"id":3255,"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/posts\/2995\/revisions\/3255"}],"wp:attachment":[{"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/media?parent=2995"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/categories?post=2995"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/tags?post=2995"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}