<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Domain-Driven AI — DDD, Architecture, and AI-Assisted Software Design]]></title><description><![CDATA[A blog about domain-driven design, AI-assisted development, bounded contexts, event-driven architecture, and building software systems that remain understandable as code becomes cheaper to generate.]]></description><link>https://blog.unplugit.fi</link><image><url>https://cdn.hashnode.com/uploads/logos/6a173a3ebadcd8afcb19d807/f2e6befb-23cc-4a76-bd7a-b7a230cc8770.png</url><title>Domain-Driven AI — DDD, Architecture, and AI-Assisted Software Design</title><link>https://blog.unplugit.fi</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 05 Sep 2026 23:55:09 GMT</lastBuildDate><atom:link href="https://blog.unplugit.fi/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Alignment Needs Bounded Ownership]]></title><description><![CDATA[When organisations talk about alignment, the conversation often moves towards shared goals, shared terminology and common ways of working.
That sounds reasonable. It can also lead to a harmful conclus]]></description><link>https://blog.unplugit.fi/alignment-needs-bounded-ownership</link><guid isPermaLink="true">https://blog.unplugit.fi/alignment-needs-bounded-ownership</guid><category><![CDATA[software architecture]]></category><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[engineering-management]]></category><category><![CDATA[Artificial Intelligence]]></category><dc:creator><![CDATA[Mike Young]]></dc:creator><pubDate>Tue, 01 Sep 2026 13:35:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a173a3ebadcd8afcb19d807/e37027ff-8d2e-4b08-b210-e43a2d9ef506.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When organisations talk about alignment, the conversation often moves towards shared goals, shared terminology and common ways of working.</p>
<p>That sounds reasonable. It can also lead to a harmful conclusion: if people are misaligned, everyone must use the same frameworks, models and follow decisions from a central place more closely.</p>
<p>Most organisations are too varied for that. A company-wide processes, models or outcomes will either remain vague or favour one part of the business. Central decisions can provide direction, but they cannot contain the local knowledge needed for every situation. Teams still need room to interpret that direction and act on what they know.</p>
<p>But on the otherhand local autonomy becomes risky when its boundaries are unclear. A team can optimise an outcome it cannot meaningfully influence or make a decision whose consequences fall on another part of the organisation.</p>
<p>Alignment needs ownership with clear, visible edges.</p>
<h2>Shared direction, different models</h2>
<p>Domain-driven design gives us a useful way to think about this. A bounded context defines where a particular language and model are valid. The word <code>Customer</code> may mean the person using a mobile application in one context, a billing account in another and a member of a loyalty programme in a third.</p>
<p>None of these models needs to become the organisation-wide truth. Each exists because people are making different decisions.</p>
<p>The same idea can be applied beyond software architecture. An organisation contains many places where decisions are made: executive leadership, business areas, product teams, platform teams, security, operations and finance. Each works with its own view of the organisation. Each also has different authority.</p>
<p>I think of the organisational version as <strong>bounded ownership</strong>.</p>
<p>Bounded ownership defines an area within which a group can interpret intent and act without waiting for central approval. It includes:</p>
<ul>
<li><p>the language and models that are valid inside it;</p>
</li>
<li><p>the outcomes it can reasonably influence;</p>
</li>
<li><p>the decisions it has authority to make;</p>
</li>
<li><p>the knowledge for which it is the owner;</p>
</li>
<li><p>the evidence it can observe;</p>
</li>
<li><p>the matters it must negotiate with another owner.</p>
</li>
</ul>
<p>An area of ownership may correspond to a team, but it does not have to. A team can have several areas of ownership, and one area may involve people from several organisational units. The useful boundary follows responsibility and authority, not the boxes in an organisation chart.</p>
<p>Local decisions now have a home, and their limits are easier to see.</p>
<h2>Responsibility without authority produces waiting</h2>
<p>Teams are often told to own outcomes while the decisions that affect those outcomes are made elsewhere and without the teams influence.</p>
<p>Imagine a checkout team that is expected to reduce purchase abandonment. It can change the user interface, adjust the order of steps and run experiments. It may not be allowed to change payment authentication rules, pricing, campaign promises or the range of available delivery options.</p>
<p>Those limits are reasonable. Payments, marketing and fulfilment have knowledge and obligations that the checkout team does not.</p>
<p>The trouble starts when the organisation talks as if the team owns the entire outcome. The team becomes accountable for something it can only partly influence. It either waits for every dependency, or it begins making assumptions about decisions owned elsewhere.</p>
<p>Bounded ownership gives a more honest account of responsibility. The checkout team can own the decisions within its influence and show where progress depends on another owner. If payment authentication appears to cause abandonment, the team can bring evidence and a proposal to the payment owner. It cannot quietly weaken authentication because its local metric would improve.</p>
<p>The team also needs access to evidence from the part of the customer journey it is expected to improve. Without that, it can make changes but cannot judge them responsibly. Authority, influence and observable evidence should describe roughly the same area. Large gaps between them cannot create real accountability.</p>
<p>Authority also needs to be real enough for the team to act. If every local choice still requires approval from a central group, the boundary describes responsibility without granting autonomy. Work slows down, and decisions move away from the people who have the most relevant knowledge.</p>
<p>Healthy alignment sits between two failures: central control over decisions that need local understanding, and local freedom without clear limits or obligations to the wider system.</p>
<h2>Boundaries make autonomy safer</h2>
<p>Local autonomy is sometimes presented as a cultural question: leaders should trust teams, and teams should take ownership. Trust matters, but it does not tell a team which decisions are actually theirs.</p>
<p>A clear ownership boundary makes autonomy more concrete.</p>
<p>Inside the boundary, people should be able to interpret intent, compare options and make choices without repeatedly asking for permission. They know which outcomes they are trying to influence and which constraints they must respect. They also own the consequences of those local choices.</p>
<p>At the boundary, the behaviour changes. A question that affects another owner becomes a negotiation rather than an internal decision.</p>
<p>Owners will still influence each other. A product team may need a new payment capability. Operations may need a product change to reduce support load. Security may need stricter controls that affect the customer experience.</p>
<p>The boundary makes the interaction visible. One group can explain its need, provide evidence and propose a change. The other owner decides how that request fits its own model, constraints and responsibilities.</p>
<p>Without that boundary, influence easily turns into accidental authority. The product team defines a security rule because it needs faster delivery. A platform team standardises a domain concept because several services use similar data. Executives choose implementation details because a strategy is not specific enough to guide local judgement.</p>
<p>Every choice may look efficient in isolation. Over time, nobody is quite sure who is allowed to change what.</p>
<h2>Owners still need each other</h2>
<p>Boundaries often raise a predictable concern. If every group owns its own language and decisions, will the organisation fragment into silos?</p>
<p>It can, if ownership becomes a reason to ignore everyone else.</p>
<p>Each area of ownership receives direction from the wider organisation and operates within shared constraints. Its choices should contribute to outcomes beyond its boundary, even though it cannot control those outcomes alone. It also has a responsibility to make relevant decisions and knowledge available to the other owners that depend on them.</p>
<p>These areas remain different because their work is different. Alignment comes from making those differences compatible enough for the organisation to act as a whole.</p>
<p>The same principle appears in contracts between software contexts. An ordering system does not need the customer system's complete internal model. It needs an agreed way to obtain the few facts required for ordering decisions. The contract protects both sides from having to share one model.</p>
<p>Organisational ownership boundaries need the same discipline. People need to know what comes from another owner, what they may decide locally and when a change requires negotiation. The boundary should be permeable, but crossing it should be deliberate.</p>
<h2>AI needs a mandate, not only access</h2>
<p>AI makes these questions harder to ignore because it can move across existing boundaries with very little friction.</p>
<p>An AI assistant may be able to read strategy documents, product metrics, architecture descriptions and source code. A coding agent may be able to edit several repositories. Access to all of that material can create the impression that the AI has one complete view of the organisation.</p>
<p>What it has is material produced within different areas of ownership, using different models and backed by different kinds of authority.</p>
<p>Giving AI more context helps it produce a more informed answer. It does not tell the AI which decisions it is entitled to make.</p>
<p>Within a clear area of ownership, AI can have useful agency. It can interpret the knowledge owned there, connect a local goal to the available evidence, develop options and support decisions. With suitable controls, it may also carry out decisions that fall clearly within that authority.</p>
<p>The boundary should limit that agency. Suppose an AI working with the checkout team concludes that changing payment authentication could reduce abandonment. It can explain the dependency, assemble the evidence and draft a proposal. It should identify Payments as the owner and ask for a decision there.</p>
<p>It should not interpret technical access, an ambiguous goal or silence from another team as permission to make the change.</p>
<p>I think of this as constrained agency. The constraint is broader than a list of tools or files the AI may access. It includes semantic and organisational authority:</p>
<ul>
<li><p>which model it may treat as valid;</p>
</li>
<li><p>which sources it may interpret as authoritative;</p>
</li>
<li><p>which options it may act on;</p>
</li>
<li><p>which decisions require another owner;</p>
</li>
<li><p>where uncertainty must become a question.</p>
</li>
</ul>
<p>These limits are useful for people as well. AI simply exposes how rarely organisations have made them explicit.</p>
<h2>A boundary should sometimes stop the work</h2>
<p>There is pressure to treat every interruption as inefficiency. A team raises a dependency, an architect asks for clarification or an AI agent stops before completing a change. It is tempting to remove the boundary so the work can continue.</p>
<p>Sometimes stopping is the correct result.</p>
<p>If nobody can identify who owns a decision, that is important information about the organisation. If two groups both believe they have authority, the conflict already exists. If a team has an outcome but no meaningful decisions within its control, its responsibility has been defined badly.</p>
<p>An AI assistant should make these gaps visible instead of smoothing them over. It can describe the missing authority, identify the affected owners and formulate the question that needs a decision. Completing the task by inventing one would only hide the alignment problem for a little longer.</p>
<p>Clear boundaries do not guarantee good decisions. A team can still optimise the wrong outcome or hold an outdated model. Boundaries do, however, keep one actor from silently deciding on behalf of another. They make local autonomy safer and give disagreements a place to surface.</p>
<p>The next question is how these areas of ownership communicate without collapsing their models into one. A decision made within one boundary often becomes knowledge that another owner needs. For that to support alignment, it must remain understandable beyond the place where it was created, while still carrying its ownership and limits with it.</p>
]]></content:encoded></item><item><title><![CDATA[Software development has always been an alignment problem]]></title><description><![CDATA[The phrase alignment problem is now strongly associated with AI safety. In that discussion, the concern is whether an AI system behaves consistently with human values and intent. That is not the subje]]></description><link>https://blog.unplugit.fi/software-development-has-always-been-an-alignment-problem</link><guid isPermaLink="true">https://blog.unplugit.fi/software-development-has-always-been-an-alignment-problem</guid><category><![CDATA[software development]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[engineering-management]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[#Domain-Driven-Design]]></category><dc:creator><![CDATA[Mike Young]]></dc:creator><pubDate>Tue, 18 Aug 2026 19:59:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a173a3ebadcd8afcb19d807/6f685147-d62c-4e72-90e1-5df3072c7192.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The phrase alignment problem is now strongly associated with AI safety. In that discussion, the concern is whether an AI system behaves consistently with human values and intent. That is not the subject of this series.</p>
<p>I am interested in an older problem inside software organisations. A business has an intent. Management turns that intent into strategies, goals and plans. Teams turn those goals and plans into software. Customers and operational systems then respond to what was actually built, which may be quite different from what anyone originally intended.</p>
<p>Software organisations have spent decades trying to improve this chain. Scrum brought teams closer to customers. DevOps brought development and operations closer together. Lean challenged work that did not create value, while product operating models asked teams to own outcomes rather than deliver projects. I have worked in organisations using parts of most of them, and each has helped with a real problem.</p>
<p>The discussion around them often becomes a choice between methods. One organisation needs more product thinking, another needs better flow, and another needs clearer architecture. Those diagnoses can all be correct. I think they are also different expressions of the same underlying problem. The alignment problem.</p>
<p>Somebody has an intent, somebody else must act on a representation of that intent, and meaning can change between the two. Misalignment occurs when the resulting behaviour satisfies the representation but misses the original outcome it was meant to produce.</p>
<h2>Methods solve a problem they cannot remove</h2>
<p>The useful parts of any given methodology are usually mechanisms for improving alignment. A sprint review lets a team compare what it built with what customers or stakeholders expected. A cross-functional team reduces the number of handovers between analysis, development, testing and operations. These mechanisms can genuinely help when the conditions they assume are present.</p>
<p>The trouble begins when the mechanism travels without the conditions that made it work. Scrum assumes that a team can learn from an increment and change what it does next. DevOps assumes that the people changing a system can observe and influence its production behaviour. Outcome ownership assumes that a team can see an outcome and has enough authority to respond to it.</p>
<p>An organisation can adopt the visible parts of a methodology while leaving its underlying assumptions unmet.</p>
<p>I have seen teams hold every expected ceremony while all the decisions had already been made months in advance. Releases still passed through a separate gate, and success still meant completing the planned scope rather than producing a meaningful change. The ceremonies made coordination more orderly, which was genuinely useful. They did not give the team a way to question whether the work was still the right work.</p>
<p>Replacing the framework would not have fixed that. The names and meetings would have changed, while the authority, information and incentives underneath them remained the same.</p>
<h2>Intent is lost one translation at a time</h2>
<p>Recently I was looking at a product organisation with more than ten developers split across three squads. They served multiple business areas through one shared digital channel. At the strategic level, the intent sounded reasonable, improve the profitability and quality of digital purchasing.</p>
<p>By the time that intent reached a development team, it had usually become a collection of features. The features became backlog items, the backlog items became code, and the code had to pass testing and a release process. Each step made the work more concrete. Each step also made the original intent a little harder to see. The process provided no feedback that could challenge the original assumptions.</p>
<p>No single translation looked obviously wrong. A strategy has to lead to choices. A team needs to break the work into pieces small enough to implement effectively. Testers need observable acceptance criteria, and a release process needs a way to decide whether a change is safe.</p>
<p>The drift appeared in what those translations left behind. A feature could enter the backlog without a baseline for the behaviour it was meant to change. Acceptance criteria could prove that the requested interaction existed without showing whether customers could use it successfully. A release could be declared complete when it reached production, even though nobody had decided when and how its effect would be evaluated.</p>
<p>Everyone could attend the same planning session and receive the same slide deck. That did not prevent the loss. The strategy, backlog item and test case served different decisions, so each preserved a different part of the original meaning.</p>
<p>Misalignment can grow even if everyone understands every word they are given.</p>
<h2>Local success can make the whole worse</h2>
<p>The organisation could produce good software. Its mobile application had high customer satisfaction, and its user base was growing steadily. Experienced developers cared about quality, and a manual testing stage caught defects that would otherwise have reached customers.</p>
<p>The process was built to protect customers. That is what it did.</p>
<p>The same process meant that the fastest changes took about a month from merge to production. Larger changes could take more than six months. Because releases were expensive, teams accumulated more changes in each one. Because each release contained more changes, it required broader testing and carried more risk.</p>
<p>Every local decision was defensible. Developers completed their tickets. Testers reduced escaped defects. Projects controlled scope. Looking at the day-to-day work, it all seemed fine. The larger system still became worse at discovering whether it was building the right thing.</p>
<p>A scope metric would have hidden that problem. It could show steady progress while customer assumptions remained untested for months and each release increased the blast radius of a wrong decision.</p>
<p>Misalignment does not require incompetence or neglect. Local rationality is enough.</p>
<h2>Alignment does not require one shared model</h2>
<p>When this problem becomes visible, the natural response is to ask for stronger alignment. That can quickly become more central planning, common terminology and another coordination layer. I do not think uniformity is the answer.</p>
<p>A strategy group, a product team, a payment service and an operations function make different decisions. They need different representations of the same organisation.</p>
<p>The same issue appears in software architecture. A Customer context may define a customer as a person with a loyalty relationship. Ordering may care about the party placing and paying for an order. Forcing both into one enterprise <code>Customer</code> model creates apparent consistency, but it hides a difference that matters.</p>
<p>The models must connect. They do not have to be identical.</p>
<p>This is one of the ideas I explored in the <a href="https://blog.unplugit.fi/series/domain-driven-ai">Domain-Driven AI</a> series. Bounded contexts give language and meaning a place to belong, rather than asking the entire organisation or codebase to share one model.</p>
<p>I think organisational alignment needs a similar idea. The question is not how to make every team think alike. It is how to let teams make different local decisions without losing their connection to a wider intent.</p>
<p>That shifts the problem towards boundaries and authority. It also creates the first question for the rest of this series, which decisions should a team own, and where should it have to stop and involve another owner?</p>
<h2>AI makes ambiguity executable</h2>
<p>AI enters this series as a possible aid to alignment in software organisations, not as the subject of the AI safety alignment problem. It matters because it can act on unclear knowledge much faster than people usually can. That speed works in both directions.</p>
<p>A person can read a strategy promising seamless customer experiences and recognise that it gives little guidance for a concrete trade-off. An AI system can quickly turn the same words into goals, plans, backlog items and code. The result may look coherent even though the missing decision is still missing.</p>
<p>I have seen the software version of this with coding agents. Given a request to expose a customer's loyalty level in an order flow, an agent can find an existing customer type, update the endpoint and produce passing tests. It has followed the strongest evidence available in the repository. It may also have crossed an ownership boundary and turned an internal model into a public contract.</p>
<p>Developers make the same shortcut. I certainly have, especially when creating a lot of code to keep models separate felt disproportionate to a small change. AI changes the scale of the problem. It can carry one unclear assumption through twenty files before anyone has understood the assumption it is spreading.</p>
<p>The same capability could help in the other direction. AI can point out that a strategy contains no measurable outcome, that two parts of the organisation use <code>customer</code> differently, or that a backlog item has no visible connection to its stated purpose. It can make a missing decision visible and return it to the people who own it.</p>
<p>That is the opportunity I want to explore. AI makes ambiguity executable. Used carefully, it may also make ambiguity harder to hide.</p>
<h2>Alignment has to remain unfinished</h2>
<p>A more detailed chain of instructions from executives to code would not remove this problem. A precise metric can become a target. A clear contract can preserve the wrong meaning perfectly. Some errors only become visible after people and software act together in the real world.</p>
<p>This leaves three questions for the rest of the series.</p>
<p>The first is where decisions should live, and how a team can have real autonomy without quietly taking authority that belongs elsewhere.</p>
<p>The second is how strategies, goals, facts and decisions can cross organisational boundaries without becoming anonymous text that AI can interpret however it likes.</p>
<p>The third begins when production evidence contradicts the original plan. Feedback only matters if it can travel back far enough to change a team decision, a metric or even the strategy itself. That learning loop has to become the organisation's self-correcting mechanism.</p>
<p>I do not yet know what a complete AI-assisted version of this system looks like, or where assistance quietly becomes authority. I am more certain about the underlying problem. Software development has always depended on imperfect representations of intent, and faster execution does not make those representations more truthful.</p>
]]></content:encoded></item><item><title><![CDATA[AI-assisted development needs boundaries]]></title><description><![CDATA[Most writing about AI-assisted development seems to settle into three positions. There is the productivity account, where an agent is judged by how quickly it turns a ticket into code, the prompting a]]></description><link>https://blog.unplugit.fi/ai-assisted-development-needs-boundaries</link><guid isPermaLink="true">https://blog.unplugit.fi/ai-assisted-development-needs-boundaries</guid><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[ai agents]]></category><dc:creator><![CDATA[Mike Young]]></dc:creator><pubDate>Wed, 12 Aug 2026 09:05:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a173a3ebadcd8afcb19d807/69102413-3e27-458b-87a4-45589c2c51de.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most writing about AI-assisted development seems to settle into three positions. There is the productivity account, where an agent is judged by how quickly it turns a ticket into code, the prompting account, where better instructions are expected to produce better engineering, and the safety account, where review and tests catch whatever it gets wrong. I recognise something useful in all three, but none quite describes the concern I have run into when a change reaches across a system.</p>
<p>The code usually works. I have watched an agent satisfy a request by reusing a type from another domain, passing the same model from an incoming API to persistence, and adding a field to a published response because the internal object already contained it. The implementation was efficient, the tests were green, and the architecture was a little less honest afterwards.</p>
<p>Developers make the same compromises. A developer who has spent two years in a system may remember why two similar <code>Customer</code> classes were kept separate, or why a reachable table belongs to another team. The agent sees imports, public methods and repeated patterns. When a repository contains the intended architecture and five years of exceptions, the exceptions may be its clearest instructions.</p>
<p>What concerns me is therefore less about whether an agent can generate a change and more about what keeps that change inside the part of the system where it belongs. That leads back to bounded contexts, published contracts and ownership, which are older concerns than generative AI. AI has merely increased the speed at which an unclear boundary can be crossed.</p>
<h2>The repository teaches the shortest path</h2>
<p>A coding agent learns much of the architecture from the code around a task, which makes the repository both its most useful source of context and its most misleading one. If five endpoints return persistence objects directly because the first was written under time pressure, a sixth implementation has strong evidence that this is the house style, even when the team has spent a year separating API, domain and persistence models.</p>
<p>The agent can find conventions nobody documented and reproduce a Spring Boot integration pattern without needing every decision restated in a prompt. I have benefited from that pattern recognition where the ceremony is predictable and the design has already been decided.</p>
<p>The cost is that frequency starts to look like intent. A direct database read added during an incident or an internal DTO leaked into an OpenAPI response can appear more authoritative than an architecture diagram nobody updates. The flattering account is that the agent understands the codebase, the more accurate one is that it understands what the codebase repeatedly permits.</p>
<p>A bounded context reduces that ambiguity by saying where a model applies, who owns it and how its meaning is published. A box labelled <code>Ordering</code> in a diagram, however, is not yet a development constraint.</p>
<h2>A bounded task changes the answer</h2>
<p>I have used a deliberately ordinary example when testing this distinction, adding a customer's loyalty level to an order flow. With only that sentence, an agent has several plausible routes, it can import the customer context's internal <code>Customer</code> type, query the customer store or add another shared model. Every route can produce the requested field.</p>
<p>The answer changes when the task states that Ordering owns the order, Customer owns loyalty status, and Ordering may learn about that status only through a published contract. Ordering can consume a customer event, keep a consumer-owned projection containing the few facts it needs and translate those facts into its own language. A missing field in the event then becomes a visible contract question rather than permission to reach into somebody else's data.</p>
<p>This creates more types. The external model records what Ordering receives, the internal model what an order means, and the contract model what Ordering publishes. Before coding agents, I sometimes accepted one Kotlin data class flowing through all three roles because the mappings and tests felt disproportionate to a small change.</p>
<p>AI has weakened that excuse. It can generate the types, mappings and tests, leaving the design decision exposed instead of hiding it behind boilerplate. The separation also limits accidental data exposure, because an internal fraud indicator cannot drift into a public response merely because the serializer found it. Generated mapping code is cheap, an accidental contract is not.</p>
<h2>Written guidance needs hard edges</h2>
<p>My first attempt to provide architectural context was one large Markdown file at the root of the repository. It became less useful as it accumulated unrelated instructions. A narrower hierarchy has worked better, the repository describes the context map, each bounded context records its language, owned data and contracts, and module-level notes contain genuinely local constraints.</p>
<p>This material helps both agents and people entering the codebase, but it creates another synchronisation problem. A sentence saying that Ordering never imports Customer internals has little value once the Gradle dependency graph allows it and six months of code demonstrate otherwise.</p>
<p>I treat written guidance as an explanation of intent, while deterministic checks provide the edge. ArchUnit can reject forbidden dependencies, OpenAPI and AsyncAPI schemas can validate what crosses a boundary, and contract or security tests can expose incompatible and accidental changes. None understands the domain, but each can preserve an accepted decision after the conversation that produced it has been forgotten.</p>
<p>AI can help maintain these constraints, generate a dependency rule from an established policy, add contract tests alongside a schema change, or point out where the context description and the code disagree. It should not quietly reconcile that disagreement, because choosing which side is correct is an architectural decision.</p>
<h2>Capability has grown faster than authority</h2>
<p>A coding agent can inspect a repository, edit twenty files and run a Gradle build while I am still holding the requirement in my head. That reach is useful inside one bounded context. Across Customer, Ordering and Payments, it can hide three ownership decisions inside one coherent-looking change.</p>
<p>A consumer can explain that it needs a new fact and propose an event field, but it does not get to redefine the producer's language or update its database. An agent working for the consumer has no reason to receive more authority than the team that asked it to act.</p>
<p>This is why file count is a poor proxy for risk. A twenty-file mechanical refactoring behind an existing contract may preserve meaning, while a one-line addition to an AsyncAPI schema may publish a new domain fact to every consumer. The second change looks smaller and may deserve the larger conversation.</p>
<p>Respecting ownership means that work sometimes stops at a boundary. The agent may produce a contract proposal instead of a completed feature, leaving another team to decide whether the fact belongs in its published language. That delay can be frustrating, but it makes a hidden decision visible. An agent being able to edit a file does not mean the task gave it the right to change what that file means.</p>
<h2>Bounded contexts keep speed contained</h2>
<p>AI has made some kinds of coding enjoyable for me again after more than twenty years of development work, particularly when repetitive implementation no longer interrupts the design. It has also made it possible to produce a large change before I have formed a reliable mental model of everything it touches. Faster generation increases the value of a smaller blast radius.</p>
<p>That is where this series has led me. More material was not better context until it was scoped. AI could not become the source of truth because the truth still needed an owner. Contracts made a context visible at its edges, while separate external, internal and contract models kept one meaning from sliding into another. Each argument depended on the same underlying idea, bounded contexts really do matter, perhaps more when an agent can move through a codebase faster than the people responsible for it.</p>
<p>The useful goal is therefore not only to help an agent understand a bounded context, but to make it work within one. The context should define the language it may use, the data it may change, the contracts through which it may collaborate and the decisions that require another owner. Repository guidance can explain those limits, and executable architecture rules can make the agent enforce them while it works.</p>
<p>A good agent-assisted change should be able to move quickly inside an established boundary and become deliberately slower at its edge. It should generate the mappings instead of collapsing the models, add the contract checks instead of bypassing the contract, and stop with a proposal when the next step belongs to another context. That does not remove architectural judgement, but it keeps the consequences of one task from spreading silently through the system.</p>
<p>A well-enforced boundary can still preserve the wrong model, and domain understanding still changes through conversations and production evidence. I do not yet know how much of that judgement coding agents will eventually share. For now, I want them to help enforce the boundaries we have deliberately chosen, not erase those boundaries simply because generating the larger change has become easy.</p>
]]></content:encoded></item><item><title><![CDATA[Internal, External, and Contract Models Should Not Be the Same Thing]]></title><description><![CDATA[Discussions about model separation often end up being discussions about boilerplate. One object arrives at a service boundary, most of its fields are copied into another object, and a similar conversi]]></description><link>https://blog.unplugit.fi/internal-external-and-contract-models</link><guid isPermaLink="true">https://blog.unplugit.fi/internal-external-and-contract-models</guid><category><![CDATA[AI]]></category><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[Bounded Contexts]]></category><category><![CDATA[AI-assisted development]]></category><dc:creator><![CDATA[Mike Young]]></dc:creator><pubDate>Thu, 06 Aug 2026 07:14:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a173a3ebadcd8afcb19d807/5f760998-31b7-4d94-9642-6fd59b024b45.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Discussions about model separation often end up being discussions about boilerplate. One object arrives at a service boundary, most of its fields are copied into another object, and a similar conversion happens again before data leaves the service.</p>
<p>I have never particularly enjoyed writing that code. I have also taken the shortcut of using one model throughout an application. In a small or short-lived system, that can be a defensible decision.</p>
<p>The trouble usually appears later.</p>
<p>The same class gradually becomes an API request, an event payload, a persistence structure, and the internal representation of the domain. A field cannot be renamed because an external consumer may depend on it. An upstream system introduces a nullable value, so nullability spreads into business logic. Domain objects collect serialisation annotations because they also need to cross a technical boundary.</p>
<p>At first, one model looked simpler. Eventually, every change seemed to affect something it should not.</p>
<p>This is where I think the usual argument about duplicate fields misses the point. The important difference between these models is not their structure. It is who owns their meaning and why they need to change.</p>
<h2>Similar structure hides different responsibilities</h2>
<p>Software models are easy to compare by looking at their names, fields, types, and relationships. When two classes contain nearly the same data, the duplication is obvious. Their different responsibilities are less visible.</p>
<p>Consider a product.</p>
<p>The product context may own descriptions, classifications, lifecycle state, packaging information, and sales restrictions. An ordering context may only need to know whether an item can currently be sold through a particular channel.</p>
<p>Both contexts might use the name <code>Product</code>. Their models might share an identifier and a few fields. They still do not represent the same understanding.</p>
<p>The product context owns the model it uses to manage products. The ordering context owns the smaller interpretation it needs to make ordering decisions.</p>
<p>I have found ownership more useful than structural similarity when deciding whether models should be shared. The relevant questions are who decides what a field means, which decisions the model supports, and what can cause it to change.</p>
<p>Two models can look identical today and still need the freedom to become different tomorrow.</p>
<h2>The internal model needs room to change</h2>
<p>The internal model belongs to the bounded context. It contains the language and rules the context needs to do its work. Its shape should follow the domain rather than an API framework, database library, or message serializer.</p>
<p>I do not see a domain model as a finished description of reality. It is closer to a working theory.</p>
<p>A team models what it currently understands and builds around it. Later, the work exposes mistakes. A boolean turns out to be the result of a policy. One entity turns out to contain two concepts that happen to share an identifier. A field that once appeared important has no part in an actual business decision.</p>
<p>The model should be allowed to follow that learning.</p>
<p>That becomes harder when external consumers depend on the same structure. An improvement to the internal model becomes a contract change. The team starts preserving concepts that no longer fit because removing them might break somebody else.</p>
<p>Transport concerns arrive by the same route. Fields become nullable because an API permits them to be absent. Generated types enter business logic because using them avoids a conversion. Each decision looks harmless on its own.</p>
<p>Together, they make the internal model describe the history of its integrations instead of the context's current understanding.</p>
<h2>Both sides of a boundary need translation</h2>
<p>The same problem appears when data enters a context.</p>
<p>A generated API client or event class already contains the required fields, so passing it directly into application and domain logic feels efficient. No local type is needed. No mapping needs to be maintained.</p>
<p>It also allows the producer's model to enter the consumer unchanged.</p>
<p>The producer owns the facts it publishes. It does not own what those facts mean inside another context. That interpretation belongs to the consumer.</p>
<p>If a customer context publishes a large <code>CustomerUpdated</code> event, an ordering context may only care about the customer identifier and whether ordering is currently allowed. Copying the full customer structure does not give the ordering context a better model. It gives it more upstream changes to absorb and more concepts that have no role in ordering.</p>
<p>A consumer-owned external model can be smaller. It can use local language, combine information from several messages, or reshape an upstream value into something useful for a local decision. This is the anti-corruption layer in a fairly ordinary form: a deliberate translation at the boundary.</p>
<p>The contract model has a related but different responsibility. It defines what the context intentionally accepts from or promises to others. A contract may remain stable while the internal implementation changes substantially. It may reveal less than the context knows, or preserve an older representation because compatibility matters more than internal elegance.</p>
<p>Publishing an internal entity directly makes all of its visible details available for other systems to depend on. A field may be stored by consumers. Its absence may acquire meaning. Its name may enter the language between teams.</p>
<p>Once that happens, it is no longer an implementation detail, even if nobody meant to publish it.</p>
<h2>Mapping makes hidden decisions visible</h2>
<p>Separating models creates mapping code. Sometimes it creates a surprising amount of it.</p>
<p>That cost is real. Several identical classes do not improve a design merely because they sit in different packages.</p>
<p>Still, I have become less convinced that mapping is only boilerplate. A mapping decides what a missing value means. It decides whether an external status maps directly to an internal state or contributes to a policy decision. On the way out, it decides which internal details are part of the promise made to consumers.</p>
<p>Those decisions exist even when no mapper exists.</p>
<p>Without an explicit translation, they tend to hide in deserialisation settings, nullable domain fields, convenience methods, and assumptions scattered through handlers. The code is shorter, but the translation has not disappeared. It has become harder to find.</p>
<p>I would rather see that decision in one slightly boring function than discover parts of it across five places in the application.</p>
<p>Some duplication remains wasteful. If two models have the same owner, express the same meaning, and change for the same reasons, separating them achieves little. But when those conditions differ, repeated fields can be cheaper than shared meaning that nobody quite owns.</p>
<h2>Separate models narrow the security surface</h2>
<p>Model separation also gives security decisions a visible place.</p>
<p>Suppose an internal object gains a field such as <code>approved</code>, <code>riskLevel</code>, <code>accountRole</code>, or <code>manualReviewRequired</code>. If the object also serves as an API request, a caller may suddenly be able to provide a value that was intended to be controlled only inside the context.</p>
<p>The value may deserialize correctly. It may even pass format validation. The problem is that the caller should never have been able to set it.</p>
<p>An explicit input contract defines the part of the internal state that external input is allowed to influence. It does not replace validation or authorisation, but it makes the accepted surface deliberate.</p>
<p>The same risk exists on the way out. Internal models accumulate information that should remain inside the context: personal data, fraud indicators, operational flags, access decisions, pricing details, or intermediate policy results.</p>
<p>When an outbound contract is assembled explicitly, exposing a new field requires a mapping change. When an internal object is serialised directly, exposure can happen as a side effect of an unrelated domain change.</p>
<p>Separate models do not make a system secure by themselves. They create a boundary where influence and disclosure can be reviewed.</p>
<h2>AI makes model separation cheaper</h2>
<p>AI-assisted development makes the shortcut easier to take.</p>
<p>From the code alone, reuse often looks clean. Similar fields suggest one type. Generated clients already contain the required data. An internal entity can already be serialised. The result has fewer classes, fewer mappings, and fewer tests.</p>
<p>I cannot really blame the tool for following what is visible.</p>
<p>Structural similarity is visible in code. Ownership usually is not. A coding agent cannot infer from field names that one model is an external promise, another is a consumer-owned interpretation, and a third needs to evolve with the domain. It will not know that a new internal field is sensitive unless that constraint appears somewhere it can inspect.</p>
<p>Developers have been collapsing models for decades. AI only makes the choice faster and easier to repeat across a codebase.</p>
<p>It also makes deliberate separation cheaper. Once the responsibility of each model is clear, AI can generate types, mapping functions, validation, conversion tests, and serialisation tests. It can update adapters when a contract changes. Architecture checks can keep generated client types and contract types out of the domain model.</p>
<p>The difficult decisions remain human ones. Structural similarity does not reveal what an external value means inside the domain. It does not decide which fields a caller may influence or which internal details are safe to publish.</p>
<p>The repository therefore needs to make the intended flow visible. Package and module boundaries can distinguish published contracts, consumer-owned external models, and the internal domain. Dependency rules can require translation before information enters or leaves domain logic. Short documentation can explain who owns each model and why it exists.</p>
<p>The exact package names matter less than the rule they make visible.</p>
<p>I still do not like mapping code very much. But AI has weakened the practical argument for avoiding it. The mechanical work has become cheaper, while the cost of accidental coupling and accidental disclosure has not.</p>
<p>That does not mean every service needs three versions of every object. I still do not know where the separation stops being worth its cost. In a small application with one owner and one reason for change, a shared model may be entirely reasonable.</p>
<p>The distinction becomes important when ownership, meaning, security exposure, or reasons for change begin to diverge. At that point, the models should be allowed to diverge too.</p>
<p>AI can help maintain that boundary. It cannot decide that the boundary matters unless the architecture makes the decision visible.</p>
]]></content:encoded></item><item><title><![CDATA[Contracts Are Where Context Becomes Visible]]></title><description><![CDATA[Most writing about bounded contexts pays close attention to what happens inside the boundary. The model should use the language of the domain. Business rules should have a clear home. Concepts that me]]></description><link>https://blog.unplugit.fi/contracts-are-where-context-becomes-visible</link><guid isPermaLink="true">https://blog.unplugit.fi/contracts-are-where-context-becomes-visible</guid><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[AI]]></category><category><![CDATA[event-driven-architecture]]></category><category><![CDATA[api]]></category><dc:creator><![CDATA[Mike Young]]></dc:creator><pubDate>Thu, 25 Jun 2026 06:42:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a173a3ebadcd8afcb19d807/ca99220f-424e-41ba-8652-a09961cec1ca.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most writing about bounded contexts pays close attention to what happens inside the boundary. The model should use the language of the domain. Business rules should have a clear home. Concepts that mean different things should be kept apart.</p>
<p>That work matters. I have seen how quickly a system becomes difficult to change when the internal model loses those distinctions.</p>
<p>I am more interested here in another part of the context: the surface it presents to everyone else.</p>
<p>Other teams do not see the aggregates or the private concepts that make the implementation coherent. They meet an API, a command, an event or a schema. From there, they decide what the context knows and what they can depend on.</p>
<p>The contract is where the context becomes visible.</p>
<p>AI makes that surface more consequential. It can generate clients, handlers, tests and documentation from a contract with very little effort. This is genuinely useful. It also allows unclear language to travel further before anyone notices what is missing.</p>
<h2>The boundary is a published model</h2>
<p>Contracts are often treated as technical artefacts. An OpenAPI definition describes HTTP operations. An AsyncAPI schema describes messages. A DTO gives two systems a structure they can both serialise.</p>
<p>All of that is true, but it leaves out the part I have become more interested in.</p>
<p>A contract publishes part of the context's understanding. It tells the outside world what the context accepts, what it is prepared to state as true and which concepts it owns. It does not expose the whole domain model. It exposes the part that other contexts are allowed to build on.</p>
<p>Consider an event called <code>OrderUpdated</code>.</p>
<p>The name says that something changed, while avoiding any claim about what is now true. The payload may contain enough fields to reconstruct the answer. The documentation may describe the common cases. The people involved in the original integration may simply remember what they agreed.</p>
<p>That can work for a while.</p>
<p>Then another consumer arrives. A dashboard starts using the event. A support process depends on one of its fields. Someone points an AI tool at the schema and asks it to create a handler. Each new use gives the vague event a little more authority.</p>
<p>The missing distinction may be between an order being confirmed, cancelled, rejected or adjusted. Those are different facts. Consumers can depend on them in different ways. If the contract does not express the distinction, every consumer has to recover it from somewhere else.</p>
<p>The internal model may be precise while the published model remains vague.</p>
<h2>AI lets ambiguity travel further</h2>
<p>Weak contracts caused problems long before AI. Consumers depended on accidental fields. Internal structures leaked into public APIs. Events became generic change notifications because naming the business fact took more work.</p>
<p>The friction around those weaknesses used to reveal some of them. A consumer team had to ask what an event meant. Someone writing a client had to inspect the examples. A documentation gap caused a conversation.</p>
<p>AI can remove much of that friction.</p>
<p>When a contract is vague, the model rarely stops at the vague part. It draws intent from names, types, examples, nearby code and whatever documentation happens to be available. The generated result can look more complete than the source material deserves. The code reads well. The tests pass. The explanation sounds settled.</p>
<p>I find this more worrying than an obvious generation failure. Missing meaning does not necessarily produce broken software. It can produce working software built on an assumption that nobody made explicit.</p>
<p>A precise contract gives AI a smaller space in which to infer. A vague one still gives it plenty to generate, but much less to justify what it generated.</p>
<p>AI is unusually willing to continue where a human integration discussion might have paused.</p>
<h2>Exported internals weaken the boundary</h2>
<p>One common shortcut is to generate the external contract directly from the internal model. I understand the appeal. The fields and types already exist. Reusing them avoids mappings and duplicated structures. In a small application, the trade-off may be entirely reasonable.</p>
<p>Across bounded contexts, the two models have different responsibilities.</p>
<p>The internal model serves the rules and decisions of the owning context. Its language can be rich in places that matter only inside that boundary. It can change as the team learns more about the domain.</p>
<p>The contract serves communication across the boundary. It needs to be deliberate about what it reveals, what it hides and what other contexts may rely on. Some internal distinctions are irrelevant outside. Some explanations needed by consumers have no natural place in the internal type.</p>
<p>Generating one from the other can hide this design decision. The result looks consistent because the same names and structures pass through every layer. It may also expose implementation choices as promises to consumers.</p>
<p>AI tooling tends to make direct reuse even more attractive. One schema can become a model, an API and a client with very little visible effort. The saved mapping code is easy to count. The lost freedom at the boundary is harder to see.</p>
<p>Some duplication is the cost of keeping meanings separate.</p>
<h2>Commands and events expose ownership</h2>
<p>Commands and events make the published model easier to see because they speak in different directions.</p>
<p>A command names something the context is willing to be asked to do. <code>UpdateOrder</code> leaves most of the intent outside the contract. <code>ConfirmOrder</code>, <code>SelectDeliveryTime</code> and <code>CancelOrder</code> bring more of the business action into the language of the boundary.</p>
<p>This is useful even when the names are not perfect. The command makes it clearer that the caller is asking the owning context to apply its rules. It is not requesting a direct mutation of someone else's data.</p>
<p>An event carries a different claim. It states something the context is prepared to publish as true. <code>PaymentCaptured</code> gives a consumer a fact to reason about. <code>PaymentUpdated</code> tells the consumer to look elsewhere for the meaning.</p>
<p>Not every event represents a major business milestone. Some distribute data. Others signal progress in a process. Problems begin when those different kinds of messages are made to look interchangeable. A consumer then has to guess whether it has received a durable business fact, a current data snapshot or a notification that more work may follow.</p>
<p>The contract reveals whether the producing context has made that distinction itself.</p>
<h2>A contract should leave less to infer</h2>
<p>Clear contracts do not have to be large. They need enough language to remove the guesses that matter.</p>
<p>Names carry part of that work. Examples, constraints and short explanations carry the rest. A timestamp may represent business time or processing time. An event may be emitted before the wider process has finished. Neither distinction is visible from the type alone.</p>
<p>These details are sometimes treated as supporting documentation. I think they belong closer to the contract because they change what the contract means.</p>
<p>They also change what AI can reasonably produce from it. A schema with vague names gives a model structure. A schema with ownership, constraints and examples gives it a boundary.</p>
<p>I do not think the answer is a heavy review process. The useful habit is to read a proposed contract as language as well as an integration mechanism. I try to look at what the outside world can now see, what the names appear to promise and which conclusions a consumer could draw without access to the implementation discussion.</p>
<p>Most future consumers will not have been in that discussion. Neither will the AI tool pointed at the repository six months later.</p>
<h2>The visible model deserves care</h2>
<p>I still care deeply about the model inside a bounded context. Poor internals eventually show up as scattered rules, inconsistent language and changes that take longer than they should.</p>
<p>The contract creates a different kind of consequence. It shapes how other contexts understand the boundary. Those consumers turn its names and structures into their own assumptions. AI now helps them do that faster and at a larger scale.</p>
<p>This makes contracts more important in AI-assisted development, although not because schemas have suddenly become architecture. Their meaning has always mattered. What has changed is the speed at which the visible model can be reused and amplified.</p>
<p>A clear contract gives AI something defensible to work from. A vague contract gives it room to invent coherence.</p>
<p>I am still unsure how much meaning belongs directly in a schema and how much needs to live in material around it. The boundary will never explain the whole context. It should at least make clear which parts of the context the outside world is allowed to believe.</p>
]]></content:encoded></item><item><title><![CDATA[AI Should Not Become the Source of Truth]]></title><description><![CDATA[Most discussion about AI-generated documentation focuses on accuracy. Can the model find the right information? Does it hallucinate? Can its answer be verified?
Those concerns matter, but I have becom]]></description><link>https://blog.unplugit.fi/ai-should-not-become-the-source-of-truth</link><guid isPermaLink="true">https://blog.unplugit.fi/ai-should-not-become-the-source-of-truth</guid><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[architecture]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Mike Young]]></dc:creator><pubDate>Tue, 09 Jun 2026 12:35:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a173a3ebadcd8afcb19d807/93896de5-e491-442b-b481-596b3546f3aa.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most discussion about AI-generated documentation focuses on accuracy. Can the model find the right information? Does it hallucinate? Can its answer be verified?</p>
<p>Those concerns matter, but I have become more worried about a quieter failure.</p>
<p>I have repeatedly asked AI to explain part of a software system by reading its code, documentation and tests. The result is often useful. It connects details that were spread across several places and produces a clearer explanation than any single source provided.</p>
<p>At first, that feels like progress.</p>
<p>The discomfort comes later, when the explanation starts to feel more authoritative than the material behind it. It is coherent, easy to reuse and written with none of the uncertainty present in the system itself.</p>
<p>AI can make unsettled knowledge look settled.</p>
<h2>A coherent answer can acquire authority</h2>
<p>Most teams already have gaps in their shared knowledge.</p>
<p>Some rules are written down. Others are enforced in code or captured indirectly in tests. A few are remembered by people who have worked with the system long enough to know its history. Some are visible only through the way the system behaves.</p>
<p>AI is good at smoothing these fragments into a readable answer. That is part of what makes it useful.</p>
<p>Suppose an explanation says that an order can be cancelled until payment has been captured. The current implementation may support that claim. There may also be tests that expect the same behaviour.</p>
<p>Still, neither tells me whether this is an agreed business rule or simply how the implementation happens to work today.</p>
<p>That uncertainty is easy to lose once the explanation has been written cleanly. Someone reuses it in a design discussion. Later, it appears in documentation. The next person treats that document as input for another piece of work.</p>
<p>Nobody explicitly decided that AI should define the cancellation rule. Its explanation became authoritative because it was the easiest version to understand and reuse.</p>
<p>The individual steps are reasonable. The authority emerges through accumulation.</p>
<h2>Meaning still needs an owner</h2>
<p>This is where I find Domain-Driven Design useful in a very practical way.</p>
<p>A bounded context gives particular words and rules a place where their meaning is owned. If the ordering context owns order cancellation, then the team responsible for that context must be able to say what cancellation means in its model.</p>
<p>Other contexts may call its API, react to published events or maintain local projections. They can depend on what the ordering context publishes, but they should not redefine the rule from the outside.</p>
<p>I think AI needs to work within the same boundary.</p>
<p>It can interpret the ordering context. It can locate code that appears to enforce a rule and tests that demonstrate the current behaviour. It can also draft a clearer explanation than the team has written so far.</p>
<p>What it cannot do is turn that interpretation into an owned domain rule by itself.</p>
<p>If generated prose is allowed to carry that authority, the model starts drifting away from the context that is responsible for it. The system may appear better documented while its meaning becomes less clearly owned.</p>
<h2>Generated documentation should expose its gaps</h2>
<p>I do not want AI to avoid inference. Much of its value comes from connecting information that was never organised for a single reader.</p>
<p>The problem is hidden inference.</p>
<p>When AI makes a statement about the domain, I want the answer to show what supports it. A rule found in an owned contract has a different status from behaviour inferred from code. A conclusion drawn from a test is different again from something the model could not verify at all.</p>
<p>The cancellation example could then be described more honestly:</p>
<blockquote>
<p>Cancellation before payment capture appears to be supported by the current implementation and tests. No owned business rule confirming this constraint was found.</p>
</blockquote>
<p>That answer is less polished, but more useful. It explains the observed behaviour without silently promoting it into a domain decision.</p>
<p>I have started asking for this explicitly when generating documentation:</p>
<blockquote>
<p>Require evidence for important claims. Mark unknowns as TBD instead of filling the gap. Highlight places where a decision or more clarity is needed. Ask when the source material is not sufficient.</p>
</blockquote>
<p>This makes the document look less finished. It may contain unresolved questions, missing decisions and awkward contradictions.</p>
<p>That is a fair cost. Those gaps were already part of the system. The generated document has only made them visible.</p>
<p>Sometimes that is the most valuable result of the work. Instead of producing a smoother summary, AI reveals that an important rule has no clear owner or authoritative source.</p>
<h2>Drafts need somewhere to land</h2>
<p>Generated material should remain provisional until it moves into an owned place.</p>
<p>An AI-generated explanation can become documentation after someone responsible for the context reviews and publishes it. Until then, it is a draft based on the evidence that happened to be available.</p>
<p>The same applies to generated schemas. A schema becomes a contract when the owning context accepts it, publishes it and takes responsibility for changing it. Text produced in a chat session has none of those properties on its own.</p>
<p>This boundary is easy to blur because AI makes plausible artefacts cheap. A useful draft can spread through tickets, chat messages and design documents before anyone has decided what status it should have.</p>
<p>Avoiding generated artefacts would throw away much of the benefit. I would rather make their transition into owned material explicit.</p>
<p>A draft can tolerate uncertainty. A source of truth has to make that uncertainty visible or resolve it.</p>
<h2>The source of truth may be less readable</h2>
<p>There is something unglamorous about most authoritative sources.</p>
<p>The rule may live in a contract, a policy, a decision record or the code that enforces an invariant. None of these is guaranteed to provide the clearest explanation for a person encountering the system for the first time.</p>
<p>AI can make these artefacts easier to work with. It can summarise them, compare versions and expose contradictions between them.</p>
<p>The phrase “source of truth” can still make the situation sound cleaner than it is. An owned document can be outdated. A test may preserve an obsolete assumption. Code may behave differently from the published contract.</p>
<p>Ownership does not remove those conflicts. It tells us where the conflict must be resolved.</p>
<p>When sources disagree, AI can show the disagreement and gather the available evidence. It should not settle the domain meaning by choosing whichever interpretation forms the most coherent story. That decision belongs to the people responsible for the context.</p>
<p>The gap remains useful information. It shows where the system’s knowledge has not yet been made dependable.</p>
<h2>Authority must remain visible</h2>
<p>The rule I want to preserve is simple:</p>
<p>A generated explanation may start the conversation. An owned source must finish it.</p>
<p>This does not require a new approval process for every paragraph produced with AI. It requires enough discipline to distinguish between what the model found, what it inferred and what the owning context has accepted.</p>
<p>AI remains useful in each case. The status of the output is what matters.</p>
<p>Bounded contexts must still own their meaning.</p>
<p>That leaves a practical problem for the next part of this series. A context cannot keep all of that meaning to itself. Other parts of the system need stable facts and rules they are allowed to depend on, without gaining access to the whole internal model.</p>
<p>Some of that meaning has to be published.</p>
<p>That is where contracts begin to carry more than structure.</p>
]]></content:encoded></item><item><title><![CDATA[More Material Is Not Better Context]]></title><description><![CDATA[Most discussions about context for AI-assisted development focus on how to give the model more relevant material. Larger context windows help. Better retrieval helps. So does adding the file, ticket o]]></description><link>https://blog.unplugit.fi/more-material-is-not-better-context</link><guid isPermaLink="true">https://blog.unplugit.fi/more-material-is-not-better-context</guid><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[AI-assisted development]]></category><category><![CDATA[DDD]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Mike Young]]></dc:creator><pubDate>Tue, 02 Jun 2026 11:41:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a173a3ebadcd8afcb19d807/594819ee-af35-4f77-b2da-3ae9207ab0d2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most discussions about context for AI-assisted development focus on how to give the model more relevant material. Larger context windows help. Better retrieval helps. So does adding the file, ticket or schema that explains the missing detail.</p>
<p>I have approached the problem in exactly that way. When an answer was weak, I added more input. When a design looked shallow, I added more documentation. When the model misunderstood the behaviour, I pasted in the implementation or the API contract.</p>
<p>Sometimes that fixed the problem.</p>
<p>But I also reached a point where the prompt kept growing and the answer still felt wrong. It was not obviously wrong. The model had used real material and produced a tidy explanation. It had simply connected things that people working in the domain would normally keep apart.</p>
<p>At that point, adding another document was treating the wrong problem. The material already contained enough detail. What it lacked was a clear boundary around the meanings inside it.</p>
<h2>More input can weaken the answer</h2>
<p>Adding material is a sensible response when the model lacks a fact. If it has not seen a business rule, it cannot reliably account for that rule. If it has not seen an interface, it has to guess how another system behaves.</p>
<p>The problem starts when relevant material comes from several different models.</p>
<p>A ticket may use the language of the business process. The implementation may use names inherited from an old database. An API schema may describe the same situation from the perspective of another system. Each source can be correct within its own setting.</p>
<p>Put them together without those settings, and the AI has to resolve the differences by itself. It tends to do that smoothly. Similar names become the same concept. Similar structures become interchangeable. Missing translations disappear into a plausible answer.</p>
<p>I had given the model better coverage of the system, but less certainty about what each part meant.</p>
<h2>Shared words hide different models</h2>
<p>Software systems are full of familiar words such as “Customer”, “Order”, “Product” and “Account”. They look safe because everyone recognises them.</p>
<p>In most systems I have worked with, these words do not have one clean meaning.</p>
<p>A customer may be the person who pays. In another part of the business, it may be the person receiving the goods. Somewhere else, it may mean a profile in a loyalty system. None of these meanings is necessarily wrong.</p>
<p>People who know the domain have learned where the ambiguity lives. They hesitate or ask which customer is being discussed. Sometimes they get it wrong too, but the uncertainty is at least visible.</p>
<p>AI does not bring that history into the conversation unless the context contains it. When several meanings appear in the supplied material, the model may use them as though they belong together. The result reads coherently even when the underlying model does not.</p>
<p>That is the failure I had been trying to fix by adding more input.</p>
<h2>A local language gives AI something to follow</h2>
<p>This brought me back to ubiquitous language.</p>
<p>I do not find it useful as a glossary maintained beside the actual work. Its value comes from appearing in conversations, code, tests, examples, commands, events and documentation. The same words recur because the team is trying to express the same model.</p>
<p>That gives AI a pattern it can follow. Once the language is clear, AI can be more consistent than people are. People keep old names because changing them is inconvenient. We borrow terminology from database tables, vendor APIs and previous projects. Over time, the model becomes harder to see in the code.</p>
<p>AI can help expose that drift. It can notice when documentation and code use different terms. It can keep generated tests aligned with the domain language. It can suggest a domain name where a technical name has survived by accident.</p>
<p>There is a cost to that consistency. AI will also repeat a poor language with great discipline. If the supplied material mixes several models, the model can make that mixture look deliberate.</p>
<p>I started to see that consistency as conditional. It helps only when the language supplied to the model is clear.</p>
<h2>The boundary must survive the prompt</h2>
<p>Ubiquitous language has always been local. Complex organisations contain several languages that overlap, collide and change at different speeds. A company-wide vocabulary rarely removes that reality.</p>
<p>A bounded context gives one of those languages somewhere to live. Inside the context, a word can become precise enough to use in code. Its rules can be tested. Commands and events can express it. The team can adjust the model when its understanding changes.</p>
<p>Outside the context, the same word may need a different meaning. The crossing requires translation.</p>
<p>AI makes that translation easy to skip. If an upstream event already contains fields that look useful, the quickest solution is often to reuse its structure as the local model. The code is shorter and the mapping disappears. That may be a defensible choice for a simple integration, but it also lets an external model decide how the local domain is expressed.</p>
<p>The same thing can happen in the other direction when an internal model is exposed directly as a public contract. AI is good at copying structures, and copying removes the friction that might otherwise make the boundary visible.</p>
<p>A useful context therefore needs more than relevant files. It needs to say which language is local, which concepts are owned elsewhere, and where translation is expected. The model should not have to infer the boundary from filenames and package structures.</p>
<h2>Real material can still produce the wrong model</h2>
<p>Hallucination gets more attention because it is easier to recognise. An invented API or requirement can be checked and rejected.</p>
<p>Model blending is quieter.</p>
<p>The AI can read real code, real documentation, real schemas and real examples. It can then combine them across a boundary that should have held. Nothing has been fabricated. There may be a pull request, passing tests and a clear summary of the change.</p>
<p>The weakness appears later. The next change needs another exception. A local rule depends on an upstream representation. A familiar word now has two meanings in the same codebase. Each step was reasonable in isolation, but the model has become harder to reason about.</p>
<p>I find this more worrying than an obviously bad answer. It looks like productive work while it is happening.</p>
<h2>A smaller world is often better context</h2>
<p>DDD gives me a practical way to think about AI context because it already deals with language, ownership and models that describe only part of the world.</p>
<p>For work inside a bounded context, I want the AI to know the local language, the concepts owned there and the rules that protect them. It should see how the context communicates with its surroundings. It should also know which concepts belong elsewhere.</p>
<p>I do not think every AI task will fit neatly inside one boundary. Architecture work, integration work and business processes often cross several of them. The crossing still needs to be explicit. The model should know when it is translating instead of quietly treating several languages as one.</p>
<p>I am not yet sure how much of this can be enforced through tooling and how much depends on teams keeping their models healthy. Contracts, evidence and ownership will all matter, but each raises a separate set of problems.</p>
<p>For now, when an AI answer feels smooth but slightly wrong, I no longer assume that the prompt is missing material. I first look for the boundary that the material has hidden.</p>
]]></content:encoded></item><item><title><![CDATA[Why DDD Matters More in the Age of AI-Generated Code]]></title><description><![CDATA[Much of the discussion I notice around AI-assisted development quickly turns to productivity. Generated code, completed tasks and agent autonomy are relatively easy things to demonstrate.
I am not sur]]></description><link>https://blog.unplugit.fi/why-ddd-matters-more-in-the-age-of-ai-generated-code</link><guid isPermaLink="true">https://blog.unplugit.fi/why-ddd-matters-more-in-the-age-of-ai-generated-code</guid><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[AI]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Bounded Contexts]]></category><category><![CDATA[software design]]></category><dc:creator><![CDATA[Mike Young]]></dc:creator><pubDate>Wed, 27 May 2026 21:06:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a173a3ebadcd8afcb19d807/d4306774-b04b-4903-adff-ae3abbe05b6b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Much of the discussion I notice around AI-assisted development quickly turns to productivity. Generated code, completed tasks and agent autonomy are relatively easy things to demonstrate.</p>
<p>I am not sure they tell us much about productivity.</p>
<p>If the software solves the wrong problem, producing it faster does not help. At best, those measures tell us that implementation got faster. Whether the work was useful is much harder to see.</p>
<p>Still, AI has changed something for me personally.</p>
<p>For the first time in a while, software feels more like creating again. I can start with an idea and get to something tangible quickly. I can try one implementation, throw it away, explore another direction and spend more of my time shaping the thing I am trying to build.</p>
<p>That has brought back some of the spark that made software interesting to me in the first place.</p>
<p>I like that change.</p>
<p>It has also made me more interested in Domain-Driven Design again.</p>
<h2>Faster implementation can hide weaker thinking</h2>
<p>When implementation was slower, there was some natural friction in turning an idea into software.</p>
<p>That friction was hardly a virtue. I do not miss spending hours writing repetitive code or debugging problems that a tool can now solve in seconds. Removing that work is one of the reasons I enjoy using AI.</p>
<p>But friction had a side effect. Turning an unclear idea into a large amount of working software took time.</p>
<p>AI reduces that delay.</p>
<p>I can give an AI assistant an idea that is still poorly formed and get something surprisingly complete back. It can produce code, tests and supporting documentation that agree with each other. The result can look coherent very quickly.</p>
<p>That coherence can be misleading.</p>
<p>A misunderstood business rule can still be implemented cleanly. A vague concept can have a good API. Tests can prove that the implementation behaves exactly as specified while the specification itself is based on the wrong understanding.</p>
<p>The uncomfortable part for me is that AI can make a weak assumption look established much faster than before.</p>
<p>This does not make AI the problem. The unclear thinking was already there. AI simply gives it more reach.</p>
<h2>The bottleneck moves towards understanding</h2>
<p>In the systems I work with, the difficult problems are rarely caused by an inability to write enough code.</p>
<p>They tend to appear around meaning.</p>
<p>A concept means one thing to one part of the business and something slightly different somewhere else. Ownership is unclear. A rule that looked simple turns out to have an exception nobody mentioned. Software gradually stops matching how the business actually works.</p>
<p>Those problems existed long before generative AI.</p>
<p>They also do not disappear when implementation becomes cheaper.</p>
<p>If anything, I think they become more visible as the limiting factor. Once I can produce a reasonable implementation quickly, I spend proportionally more time deciding what the implementation should actually mean.</p>
<p>That is where my thinking has shifted.</p>
<p>I used to think of the expensive part of software largely in terms of implementation effort. Increasingly, the scarce part seems to be a sufficiently clear understanding of the domain to know what should be implemented.</p>
<p>I cannot measure that shift neatly, and I am wary of pretending that I can. It is an observation from using these tools in real development work.</p>
<p>The code has become easier to produce. The difficult conversations have not.</p>
<h2>DDD gives the model somewhere to come from</h2>
<p>For a long time, I thought about DDD mainly as a way to design better software models.</p>
<p>I still think that is true, but it feels incomplete now.</p>
<p>A domain model is also a record of what we currently understand about part of the business. It gives names to concepts, makes rules explicit and forces decisions about where something belongs.</p>
<p>That understanding is never finished.</p>
<p>Real domains keep exposing things the model did not account for. An edge case appears. Someone from the business uses a word differently than the development team. A production incident reveals an assumption that was never really true.</p>
<p>The model changes as we learn.</p>
<p>I find this side of DDD more interesting in the context of AI than the usual discussion about tactical patterns. Aggregates, entities and value objects still have their place, but they are downstream of something more important: learning enough about the domain to create a useful model of it.</p>
<p>AI can make that learning loop faster as well.</p>
<p>I already use it to explore ideas, challenge an implementation and make connections across information that would take longer to work through manually. Sometimes that exposes a gap in my thinking before I have written much code.</p>
<p>Other times it confidently reinforces the assumption I started with.</p>
<p>That difference matters.</p>
<p>The useful loop is not simply idea to generated code. It is understanding to model to software, followed by whatever reality teaches us next.</p>
<p>DDD gives that loop some structure.</p>
<h2>Boundaries matter when AI has access to everything</h2>
<p>There is another effect I have noticed when working with AI. My first instinct when it lacks context is usually to give it more.</p>
<p>More code. More documentation. More instructions.</p>
<p>Sometimes that works.</p>
<p>Eventually, though, more material starts mixing things that should probably remain separate. Different parts of a system have different models, different language and sometimes different meanings for the same word.</p>
<p>This is where bounded contexts have started to look useful to me in a slightly different way.</p>
<p>A bounded context already tells us that a model and its language are valid within a particular boundary. The meaning does not need to apply everywhere.</p>
<p>That is useful for people designing software, and I suspect it is equally useful when deciding what information an AI assistant should reason over.</p>
<p>The value is quite practical. Words, rules and ownership are no longer floating around the whole system. They have a place, a context.</p>
<p>I do not yet know whether a bounded context maps neatly to the ideal context for an AI assistant. It probably does not in every case. But giving an AI a smaller, coherent part of the domain seems more promising than continually adding material and hoping it can work out which parts belong together.</p>
<p>There is more to unpack there, especially around ubiquitous language, contracts and sources of truth. I want to leave that for later rather than squeeze the whole argument into this post.</p>
<h2>Cheaper code makes domain clarity more valuable</h2>
<p>I do not think AI reduces the need for software design.</p>
<p>My experience so far points in the other direction.</p>
<p>As implementation becomes easier, more of my attention moves towards the assumptions behind it. I care more about whether the concept is understood, whether the language is shared and whether the model belongs inside the boundary where I am changing it.</p>
<p>DDD already gives us ways to work with those problems.</p>
<p>That does not mean DDD somehow solves AI-assisted development. I am not convinced there is a single method that does. Some DDD practices will probably turn out to be more useful in this setting than others.</p>
<p>What feels increasingly clear to me is that generating more software is not the interesting problem.</p>
<p>The interesting part is keeping that software connected to an understanding of the domain that can still change as we learn.</p>
<p>AI has made creating software enjoyable for me again. I would rather use that new speed to shorten the path between an idea and learning whether the idea was any good than simply increase the amount of software I can produce.</p>
<p>That is the direction I want to explore with Domain-Driven AI.</p>
<p>I am still unsure what the right boundaries for AI-assisted development will look like in practice. A bounded context designed for a software system may not automatically be the right unit of context for an AI assistant.</p>
<p>But as implementation gets cheaper, I find myself caring more about the quality of the domain model and the boundaries around it.</p>
<p>The next part of the problem starts there: how much context actually helps, and when does more material simply become noise?</p>
]]></content:encoded></item></channel></rss>