<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pavel Nikitin</title>
    <description>The latest articles on DEV Community by Pavel Nikitin (@meltori).</description>
    <link>https://dev.to/meltori</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2109450%2F2782c8d8-b2e9-4ab7-80e6-5f967dad8ac6.jpg</url>
      <title>DEV Community: Pavel Nikitin</title>
      <link>https://dev.to/meltori</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/meltori"/>
    <language>en</language>
    <item>
      <title>When Does an Entity Stop Being a Data Model?</title>
      <dc:creator>Pavel Nikitin</dc:creator>
      <pubDate>Sat, 19 Sep 2026 08:09:41 +0000</pubDate>
      <link>https://dev.to/meltori/when-does-an-entity-stop-being-a-data-model-3oop</link>
      <guid>https://dev.to/meltori/when-does-an-entity-stop-being-a-data-model-3oop</guid>
      <description>&lt;p&gt;Most backend applications start simple.&lt;/p&gt;

&lt;p&gt;You have a few tables, a few endpoints, some business logic, and an ORM or repository layer that keeps persistence manageable.&lt;/p&gt;

&lt;p&gt;Then the application grows.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;Customer&lt;/code&gt; is no longer just:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;id&lt;/li&gt;
&lt;li&gt;name&lt;/li&gt;
&lt;li&gt;email&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now it also needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an owner&lt;/li&gt;
&lt;li&gt;authorization rules&lt;/li&gt;
&lt;li&gt;validation&lt;/li&gt;
&lt;li&gt;lifecycle states&lt;/li&gt;
&lt;li&gt;optimistic concurrency&lt;/li&gt;
&lt;li&gt;events&lt;/li&gt;
&lt;li&gt;transactional outbox&lt;/li&gt;
&lt;li&gt;audit information&lt;/li&gt;
&lt;li&gt;increasingly complex persistence rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these requirements is particularly unusual.&lt;/p&gt;

&lt;p&gt;The interesting part is what happens to the architecture.&lt;/p&gt;

&lt;p&gt;At some point, the semantics of the &lt;code&gt;Customer&lt;/code&gt; entity stop living in one place.&lt;/p&gt;

&lt;p&gt;They become distributed across controllers, services, repositories, database constraints, authorization code, background jobs, and event handlers.&lt;/p&gt;

&lt;p&gt;The problem is not that the code is distributed.&lt;/p&gt;

&lt;p&gt;The problem is that the entity's infrastructure is distributed without an explicit architectural boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Entity gravity
&lt;/h2&gt;

&lt;p&gt;I think of this as entity gravity.&lt;/p&gt;

&lt;p&gt;As an application grows, more and more system concerns begin to orbit the same entity.&lt;/p&gt;

&lt;p&gt;A typical evolution might look like this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftwk8pvewj1mxfewiiogz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftwk8pvewj1mxfewiiogz.png" alt=" " width="636" height="690"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each concern is reasonable on its own.&lt;/p&gt;

&lt;p&gt;The problem appears when changing one entity-level rule requires coordinating many unrelated parts of the application.&lt;/p&gt;

&lt;p&gt;For example, suppose we add optimistic concurrency.&lt;/p&gt;

&lt;p&gt;A customer update now needs something like:&lt;br&gt;
&lt;code&gt;PATCH /customers/cus_123&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Corporation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"expectedVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application needs to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;authorize the operation&lt;/li&gt;
&lt;li&gt;validate the input&lt;/li&gt;
&lt;li&gt;check the lifecycle state&lt;/li&gt;
&lt;li&gt;verify the expected version&lt;/li&gt;
&lt;li&gt;update the record&lt;/li&gt;
&lt;li&gt;increment the version&lt;/li&gt;
&lt;li&gt;create an event or outbox entry&lt;/li&gt;
&lt;li&gt;commit the operation transactionally&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The question becomes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who owns this coordination?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What if the entity had its own architectural boundary?
&lt;/h2&gt;

&lt;p&gt;Instead of treating the entity as something that is merely passed through application layers, we can introduce an explicit entity infrastructure boundary.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fctgfmust0t5orm8v1w1j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fctgfmust0t5orm8v1w1j.png" alt=" " width="662" height="692"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The important part is not the exact implementation.&lt;/p&gt;

&lt;p&gt;The important part is the boundary.&lt;/p&gt;

&lt;p&gt;The application still owns its business decisions.&lt;/p&gt;

&lt;p&gt;The entity infrastructure owns the common mechanics and guarantees around entities.&lt;/p&gt;
&lt;h2&gt;
  
  
  A concrete mutation
&lt;/h2&gt;

&lt;p&gt;For example, the application could express an update as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;customer.update(&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="s2"&gt;"cus_123"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Corporation"&lt;/span&gt;&lt;span class="w"&gt; 
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
 &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="err"&gt;actor:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="err"&gt;expectedVersion:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt; 
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="err"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;strong&gt;actor&lt;/strong&gt; represents the authenticated user performing the operation.&lt;/p&gt;

&lt;p&gt;A possible internal flow is:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F930bh5tjrwk260sifn6d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F930bh5tjrwk260sifn6d.png" alt=" " width="647" height="693"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the current version is no longer &lt;code&gt;7&lt;/code&gt;, the operation can fail with a &lt;code&gt;ConcurrencyConflict&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The important distinction with the outbox is that the transaction records the &lt;strong&gt;intent to publish&lt;/strong&gt; the event together with the entity mutation.&lt;/p&gt;

&lt;p&gt;Actual event delivery is a separate concern handled by a delivery worker or another delivery strategy after the transaction commits.&lt;/p&gt;

&lt;p&gt;This keeps the entity mutation and the record of the event transactionally consistent without pretending that delivery itself is part of the database transaction.&lt;/p&gt;
&lt;h2&gt;
  
  
  Before and after
&lt;/h2&gt;

&lt;p&gt;Consider a traditional controller where the application coordinates everything itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;checkLifecycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;checkVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expectedVersion&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;outbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;createCustomerUpdatedEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is nothing inherently wrong with this code.&lt;/p&gt;

&lt;p&gt;For a small application, this may be exactly the right solution.&lt;/p&gt;

&lt;p&gt;The problem appears when the same coordination starts being repeated for many entities and the rules continue to evolve.&lt;/p&gt;

&lt;p&gt;With an explicit entity boundary, the application can instead depend on a higher-level contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;customerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;expectedVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expectedVersion&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference is not simply fewer lines of code.&lt;/p&gt;

&lt;p&gt;The entity-level infrastructure now has an explicit architectural owner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A practical heuristic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is no universal threshold for introducing such a boundary.&lt;/p&gt;

&lt;p&gt;But one useful signal is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If changing one entity-level rule regularly requires edits across five or more unrelated locations, it may be time to investigate a clearer boundary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Five is not a law.&lt;/p&gt;

&lt;p&gt;It is simply a practical heuristic for starting an architectural conversation.&lt;/p&gt;

&lt;p&gt;The more important question is whether the same entity-level concerns are repeatedly coordinated across different parts of the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should stay outside the entity boundary?
&lt;/h2&gt;

&lt;p&gt;An explicit boundary should not become a new place to put everything.&lt;/p&gt;

&lt;p&gt;Business semantics should remain outside the generic entity infrastructure.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;code&gt;Customer becomes "qualified" after three successful sales interactions&lt;/code&gt;&lt;br&gt;
is a business rule.&lt;/p&gt;

&lt;p&gt;The infrastructure can provide lifecycle, persistence, validation, authorization hooks, events, and concurrency mechanisms.&lt;/p&gt;

&lt;p&gt;It should not decide what "qualified" means.&lt;/p&gt;

&lt;p&gt;If the boundary starts answering domain questions such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"is this customer qualified?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;it has crossed its responsibility and risks becoming an &lt;strong&gt;Entity God Object&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The boundary should provide infrastructure for the entity, not absorb the entire domain model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is this an ORM problem?
&lt;/h2&gt;

&lt;p&gt;Not necessarily.&lt;/p&gt;

&lt;p&gt;An ORM can solve persistence and object-relational mapping very well.&lt;/p&gt;

&lt;p&gt;But persistence is only one part of the problem.&lt;/p&gt;

&lt;p&gt;Entity complexity can also involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;schema&lt;/li&gt;
&lt;li&gt;validation&lt;/li&gt;
&lt;li&gt;authorization&lt;/li&gt;
&lt;li&gt;lifecycle&lt;/li&gt;
&lt;li&gt;optimistic concurrency&lt;/li&gt;
&lt;li&gt;transactions&lt;/li&gt;
&lt;li&gt;events&lt;/li&gt;
&lt;li&gt;outbox processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this is not an argument against ORMs.&lt;/p&gt;

&lt;p&gt;It is an argument that the architectural boundary around an entity can be broader than its persistence mapping.&lt;/p&gt;

&lt;h2&gt;
  
  
  EntityBuilder
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;EntityBuilder&lt;/strong&gt; is one concrete implementation of this idea.&lt;/p&gt;

&lt;p&gt;Its purpose is to provide reusable infrastructure around entities while keeping application-specific business semantics outside the core.&lt;/p&gt;

&lt;p&gt;The implementation provides mechanisms for things such as schema, validation, authorization, lifecycle, persistence, transactions, optimistic concurrency, events, and outbox integration.&lt;/p&gt;

&lt;p&gt;The interesting question is therefore not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Should every application use EntityBuilder?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Should entity infrastructure have an explicit architectural boundary in the first place?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;EntityBuilder is one answer to that question.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost of the boundary
&lt;/h2&gt;

&lt;p&gt;An explicit entity boundary is not free.&lt;/p&gt;

&lt;p&gt;It introduces another architectural layer, another integration surface, and potentially additional operational complexity.&lt;/p&gt;

&lt;p&gt;For a small CRUD application with a handful of stable entities, introducing such a boundary may simply create more work.&lt;/p&gt;

&lt;p&gt;The approach becomes more interesting when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;entity-level rules are growing&lt;/li&gt;
&lt;li&gt;the same infrastructure is being rebuilt repeatedly&lt;/li&gt;
&lt;li&gt;several applications need similar entity capabilities&lt;/li&gt;
&lt;li&gt;concurrency and event handling are becoming important&lt;/li&gt;
&lt;li&gt;entity changes require coordination across many parts of the backend&lt;/li&gt;
&lt;li&gt;an existing application needs to evolve without a large rewrite&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The boundary should reduce architectural friction, not create architecture for architecture's sake.&lt;/p&gt;

&lt;h2&gt;
  
  
  When not to introduce an entity boundary
&lt;/h2&gt;

&lt;p&gt;You probably do not need one when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the application is straightforward CRUD&lt;/li&gt;
&lt;li&gt;entity-level rules are few and stable&lt;/li&gt;
&lt;li&gt;the team is small and owns the whole application&lt;/li&gt;
&lt;li&gt;there is little need for reusable infrastructure&lt;/li&gt;
&lt;li&gt;concurrency requirements are minimal&lt;/li&gt;
&lt;li&gt;events and asynchronous processing are not significant&lt;/li&gt;
&lt;li&gt;introducing another boundary would add more operational complexity than it removes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple application should be allowed to stay simple.&lt;/p&gt;




&lt;h2&gt;
  
  
  Explore the implementation
&lt;/h2&gt;

&lt;p&gt;EntityBuilder is a concrete implementation of the entity infrastructure approach described in this article.&lt;/p&gt;

&lt;p&gt;If you want to see how this boundary can be implemented as reusable infrastructure, you can find EntityBuilder here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://plati.market/itm/entitybuilder-1-0/6116934?utm_medium=organic&amp;amp;utm_campaign=entity_architecture_v1&amp;amp;utm_source=chatgpt.com" rel="noopener noreferrer"&gt;EntityBuilder 1.0 →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How do you test an entity boundary?
&lt;/h2&gt;

&lt;p&gt;Test the &lt;strong&gt;boundary contract&lt;/strong&gt;, not its implementation.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;replace the persistence adapter with a fake&lt;/li&gt;
&lt;li&gt;verify that ConcurrencyConflict is raised when the expected version does not match&lt;/li&gt;
&lt;li&gt;verify that authorization is checked before the operation proceeds to validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to test the guarantees exposed by the boundary rather than couple the tests to its internal implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is entity architecture?
&lt;/h3&gt;

&lt;p&gt;Entity architecture is an approach where an entity is treated as an explicit architectural boundary rather than only as a data structure or persistence model.&lt;/p&gt;

&lt;p&gt;The boundary can contain common infrastructure such as validation, authorization, lifecycle, persistence, concurrency, and event handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is entity infrastructure?
&lt;/h3&gt;

&lt;p&gt;Entity infrastructure is the reusable technical layer responsible for common entity-level mechanics and guarantees.&lt;/p&gt;

&lt;p&gt;It should provide infrastructure without absorbing application-specific business semantics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is EntityBuilder an ORM?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;An ORM primarily addresses object-relational mapping and persistence.&lt;/p&gt;

&lt;p&gt;EntityBuilder addresses a broader set of entity infrastructure concerns, including schema, validation, authorization, lifecycle, concurrency, transactions, and events.&lt;/p&gt;

&lt;h3&gt;
  
  
  How can an existing backend evolve without a rewrite?
&lt;/h3&gt;

&lt;p&gt;One possible approach is to introduce an explicit entity boundary incrementally.&lt;/p&gt;

&lt;p&gt;The existing application can continue to own its UI, workflows, and business logic while entity-level infrastructure is gradually moved behind a stable integration boundary.&lt;/p&gt;

&lt;p&gt;This does not require replacing the entire backend at once.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is optimistic concurrency?
&lt;/h3&gt;

&lt;p&gt;Optimistic concurrency assumes that conflicting updates are relatively uncommon.&lt;/p&gt;

&lt;p&gt;A mutation includes the version the caller expects to update. If the stored version has changed, the mutation is rejected rather than silently overwriting the newer state.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is optimistic locking?
&lt;/h3&gt;

&lt;p&gt;Optimistic locking is a common implementation technique for optimistic concurrency, often using a version field that must match before an update succeeds.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the transactional outbox pattern?
&lt;/h3&gt;

&lt;p&gt;The transactional outbox pattern stores an event or message record in the same database transaction as the state change that produced it.&lt;/p&gt;

&lt;p&gt;A separate delivery process then publishes the stored event.&lt;/p&gt;

&lt;p&gt;This helps avoid the situation where the entity update commits successfully but the corresponding event is lost.&lt;/p&gt;

&lt;h3&gt;
  
  
  How should entity infrastructure be separated from business logic?
&lt;/h3&gt;

&lt;p&gt;Entity infrastructure should provide reusable mechanics and guarantees.&lt;/p&gt;

&lt;p&gt;Business logic should remain responsible for domain-specific decisions and workflows.&lt;/p&gt;

&lt;p&gt;A useful test is to ask whether a rule describes &lt;strong&gt;how the system manages an entity&lt;/strong&gt; or &lt;strong&gt;what the business believes about that entity&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  When should you not introduce an entity boundary?
&lt;/h3&gt;

&lt;p&gt;When the additional boundary creates more complexity than it removes.&lt;/p&gt;

&lt;p&gt;Simple CRUD applications with stable requirements may not benefit from it. The architectural cost should be justified by growing entity complexity, reuse, concurrency, event processing, or similar requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  The real question
&lt;/h3&gt;

&lt;p&gt;At some point, an entity is no longer just data.&lt;/p&gt;

&lt;p&gt;It has become a boundary of behavior and guarantees.&lt;/p&gt;

&lt;p&gt;The real architectural question is whether that boundary exists explicitly, or whether it is scattered across the rest of the application.&lt;/p&gt;

&lt;p&gt;EntityBuilder is one attempt to give that boundary a concrete form.&lt;/p&gt;

&lt;p&gt;If your application is reaching the point where changing an entity means changing half the backend, that is the architectural problem worth examining.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>architecture</category>
      <category>programming</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
