{"id":183,"date":"2026-04-08T13:13:57","date_gmt":"2026-04-08T18:13:57","guid":{"rendered":"https:\/\/harmonic-framework.com\/?p=183"},"modified":"2026-04-08T13:13:57","modified_gmt":"2026-04-08T18:13:57","slug":"vbd-in-10-minutes-organizing-code-by-change","status":"publish","type":"post","link":"https:\/\/harmonic-framework.com\/es\/vbd-in-10-minutes-organizing-code-by-change\/","title":{"rendered":"VBD in 10 Minutes: Organizing Code by Change"},"content":{"rendered":"\n<p>Volatility-Based Decomposition begins with a simple premise: systems should be organized around how they change.<\/p>\n\n\n\n<p>Most architectural patterns organize code by technical role. Controllers live in one place, services in another, repositories somewhere else. This produces a clear taxonomy, but it does not explain how change propagates through the system.<\/p>\n\n\n\n<p>When business rules evolve, workflows shift, or infrastructure changes, these structures often require coordinated modification across multiple layers. The system reflects its organization, but not its volatility.<\/p>\n\n\n\n<p>Volatility-Based Decomposition addresses that directly by aligning architectural boundaries with the forces that actually drive change.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Axes of Volatility<\/h2>\n\n\n\n<p>In practice, most meaningful change falls into four categories:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Functional volatility<\/strong> \u2014 changes in business behavior, rules, and calculations<\/li>\n<li><strong>Non-functional volatility<\/strong> \u2014 changes in performance, scalability, reliability, and operational characteristics<\/li>\n<li><strong>Environmental volatility<\/strong> \u2014 changes in infrastructure, vendors, databases, APIs, and external systems<\/li>\n<li><strong>Cross-cutting volatility<\/strong> \u2014 changes in shared concerns such as logging, security, and observability<\/li>\n<\/ul>\n\n\n\n<p>These axes evolve independently. When they are combined within the same component, change in one axis forces unnecessary churn in the others.<\/p>\n\n\n\n<p>The purpose of decomposition is to prevent that coupling.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Component Roles<\/h2>\n\n\n\n<p>Volatility-Based Decomposition aligns those axes with structural roles:<\/p>\n\n\n\n<figure class=\"wp-block-table hf-iso-table\"><table><thead><tr><th>Role<\/th><th>Concern<\/th><th>Responsibility<\/th><\/tr><\/thead><tbody><tr><td>Manager<\/td><td>Orchestration<\/td><td>Workflow coordination and intent<\/td><\/tr><tr><td>Engine<\/td><td>Functional<\/td><td>Business logic execution<\/td><\/tr><tr><td>Resource Accessor<\/td><td>Environmental<\/td><td>External system interaction<\/td><\/tr><tr><td>Utility<\/td><td>Cross-cutting<\/td><td>Shared capabilities<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>A Manager coordinates workflow. It determines what happens and in what sequence, but it does not implement business rules and it does not reach directly into infrastructure.<\/p>\n\n\n\n<p>An Engine encapsulates business logic. It performs computation, applies policy, and returns results without awareness of workflow or external systems.<\/p>\n\n\n\n<p>A Resource Accessor isolates interaction with external systems. It translates between internal models and external protocols, shielding the rest of the system from environmental change.<\/p>\n\n\n\n<p>A Utility provides shared capability. Logging, security helpers, monitoring, and observability evolve independently of domain behavior.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Communication Discipline<\/h2>\n\n\n\n<p>The effectiveness of this decomposition depends on a single rule:<\/p>\n\n\n\n<p><strong>State flows down, results propagate up, and there are no horizontal calls within a tier.<\/strong><\/p>\n\n\n\n<p>Managers invoke Engines and Resource Accessors. Engines may call Resource Accessors. Utilities are available to all roles.<\/p>\n\n\n\n<p>Engines do not call other Engines. Resource Accessors do not call other Resource Accessors. Managers do not share state laterally.<\/p>\n\n\n\n<p>This rule preserves the independence of volatility axes. When it is violated, change begins to propagate across boundaries.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What a Well-Engineered System Gets Wrong<\/h2>\n\n\n\n<p>Many teams produce something like this. They are not writing procedural scripts. They are applying clean architecture. They have a domain model, proper interfaces, dependency injection, and repository abstractions. Payment and notification are behind ports. Infrastructure concerns are isolated from business logic.<\/p>\n\n\n\n<p>The structure is defensible. This is what careful, experienced work looks like.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">flowchart TB\n    classDef app fill:#0b57d0,color:#ffffff,stroke:#0842a0,stroke-width:2px;\n    classDef domain fill:#fbbc04,color:#202124,stroke:#c49000,stroke-width:2px;\n    classDef port fill:#188038,color:#ffffff,stroke:#146c2e,stroke-width:2px;\n    classDef external fill:#f8f9fa,color:#3c4043,stroke:#cfd1d4,stroke-width:1.5px;\n\n    AS[&quot;OrderApplicationService&quot;]:::app\n\n    OA[&quot;Order (Aggregate)&quot;]:::domain\n    VS[&quot;ValidationService&quot;]:::domain\n    PS[&quot;PricingService&quot;]:::domain\n\n    IPG[&quot;IPaymentGateway&quot;]:::port\n    IOR[&quot;IOrderRepository&quot;]:::port\n\n    PG[&quot;PaymentAdapter&quot;]:::external\n    ORDERR[&quot;OrderRepository&quot;]:::external\n\n    AS --&gt; OA\n    AS --&gt; VS\n    AS --&gt; PS\n    AS --&gt; IPG\n    AS --&gt; IOR\n\n    VS --&gt; OA\n    PS --&gt; OA\n\n    IPG --&gt; PG\n    IOR --&gt; ORDERR<\/code><\/pre>\n\n\n\n<p>Interfaces everywhere. Domain model properly isolated. Adapters behind ports. This is architecturally literate code. It will pass any review.<\/p>\n\n\n\n<p>And it still has the same fundamental problem.<\/p>\n\n\n\n<p>The <code class=\"\" data-line=\"\">Order<\/code> aggregate is shared. <code class=\"\" data-line=\"\">ValidationService<\/code> and <code class=\"\" data-line=\"\">PricingService<\/code> both depend on it. When pricing logic changes \u2014 a new discount model, a regulatory requirement, a currency rule \u2014 the aggregate changes. When the aggregate changes, validation is affected. Not because validation has anything to do with pricing, but because they share a model.<\/p>\n\n\n\n<p>The application service still coordinates everything synchronously. Payment goes through <code class=\"\" data-line=\"\">IPaymentGateway<\/code>, which is clean, but it happens in the same request as order submission. When payment SLA requirements change \u2014 timeouts, retry policy, regional routing \u2014 the order submission workflow must change with it. The interface hid the infrastructure. It did not isolate the use case.<\/p>\n\n\n\n<p>The ports give you the ability to swap implementations. They do not give you the ability to evolve use cases independently.<\/p>\n\n\n\n<p><strong>The abstraction addressed the wrong boundary.<\/strong><\/p>\n\n\n\n<p>Clean architecture asks: where does the dependency point? Volatility-Based Decomposition asks: what forces this to change? These are different questions, and they produce different structures.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Same System After Volatility-Based Decomposition<\/h2>\n\n\n\n<p>Under Volatility-Based Decomposition, the same system is structured differently.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">flowchart TB\n    classDef manager fill:#0b57d0,color:#ffffff,stroke:#0842a0,stroke-width:2px;\n    classDef engine fill:#fbbc04,color:#202124,stroke:#c49000,stroke-width:2px;\n    classDef accessor fill:#188038,color:#ffffff,stroke:#146c2e,stroke-width:2px;\n    classDef external fill:#f8f9fa,color:#3c4043,stroke:#cfd1d4,stroke-width:1.5px;\n\n    subgraph APP[&quot;Order Processing Application (Volatility-Aligned)&quot;]\n        direction TB\n\n        OM[&quot;OrderManager&quot;]\n\n        VE[&quot;ValidationEngine&quot;]\n        PE[&quot;PricingEngine&quot;]\n\n        PM[&quot;PaymentManager&quot;]\n        NM[&quot;NotificationManager&quot;]\n\n        IA[&quot;ItemAccessor&quot;]\n        RA[&quot;RulesAccessor&quot;]\n        PRA[&quot;PriceAccessor&quot;]\n        OA[&quot;OrderAccessor&quot;]\n    end\n\n    subgraph EXT[&quot;External Systems&quot;]\n        direction LR\n        IDS[&quot;Item Data Source&quot;]\n        RDS[&quot;Rules Store&quot;]\n        PDS[&quot;Pricing Data Source&quot;]\n        ODS[&quot;Order Store&quot;]\n        PG[&quot;Payment Provider&quot;]\n        NS[&quot;Notification Service&quot;]\n    end\n\n    OM --&gt; VE\n    VE --&gt; IA\n    VE --&gt; RA\n\n    OM --&gt; PE\n    PE --&gt; PRA\n\n    OM --&gt; OA\n\n    OM -.-&gt;|async| PM\n    OM -.-&gt;|async| NM\n\n    IA --&gt; IDS\n    RA --&gt; RDS\n    PRA --&gt; PDS\n    OA --&gt; ODS\n    PM --&gt; PG\n    NM --&gt; NS\n\n    class OM,PM,NM manager\n    class VE,PE engine\n    class IA,RA,PRA,OA accessor\n    class IDS,RDS,PDS,ODS,PG,NS external<\/code><\/pre>\n\n\n\n<p>Here, the <strong>OrderManager<\/strong> coordinates the workflow but does not embed business logic or infrastructure concerns.<\/p>\n\n\n\n<p>The <strong>ValidationEngine<\/strong> and <strong>PricingEngine<\/strong> operate independently. Each consumes only the data it requires through its own Resource Accessor. Neither Engine depends on the other.<\/p>\n\n\n\n<p>Persistence is isolated. External integrations are isolated. And critically, payment and notification are triggered <strong>asynchronous workflows<\/strong>, not embedded steps in the primary request path.<\/p>\n\n\n\n<p>This breaks the coupling at the use-case level.<\/p>\n\n\n\n<p>Order submission is now independent of payment processing. Payment is independent of notification. Each workflow can evolve, fail, retry, or scale without forcing coordinated change in the others.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Change in Practice<\/h2>\n\n\n\n<p>A compliance requirement arrives: high-value orders must be validated against a fraud rules engine before submission.<\/p>\n\n\n\n<p>This is a single, well-scoped requirement. It touches one domain concern: validation. Watch what happens when it lands on each system.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">In the Clean Architecture System<\/h3>\n\n\n\n<p>The fraud check result must flow from <code class=\"\" data-line=\"\">ValidationService<\/code> through the shared <code class=\"\" data-line=\"\">Order<\/code> aggregate so the application service can act on it. The aggregate changes to carry fraud status. The application service changes to branch on that status. And if fraud status gates payment authorization, the payment path changes too \u2014 not because payment logic changed, but because the shared model connects them.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">flowchart TB\n    classDef changed fill:#d93025,color:#ffffff,stroke:#b31412,stroke-width:2px;\n    classDef affected fill:#e37400,color:#ffffff,stroke:#b06000,stroke-width:2px;\n    classDef unchanged fill:#f1f3f4,color:#9aa0a6,stroke:#dadce0,stroke-width:1px;\n    classDef lChanged fill:#d93025,color:#ffffff,stroke:#b31412,stroke-width:2px,font-size:11px;\n    classDef lAffected fill:#e37400,color:#ffffff,stroke:#b06000,stroke-width:2px,font-size:11px;\n    classDef lUnchanged fill:#f1f3f4,color:#9aa0a6,stroke:#dadce0,stroke-width:1px,font-size:11px;\n\n    subgraph APP[&quot;Order Processing Application (Layered)&quot;]\n        direction TB\n\n        AS[&quot;OrderApplicationService&quot;]:::changed\n\n        OA[&quot;Order (Aggregate)&quot;]:::changed\n        VS[&quot;ValidationService&quot;]:::changed\n        PS[&quot;PricingService&quot;]:::unchanged\n\n        IPG[&quot;IPaymentGateway&quot;]:::affected\n        IOR[&quot;IOrderRepository&quot;]:::unchanged\n\n        PG[&quot;PaymentAdapter&quot;]:::affected\n        ORDERR[&quot;OrderRepository&quot;]:::unchanged\n\n        AS --&gt; OA\n        AS --&gt; VS\n        AS --&gt; PS\n        AS --&gt; IPG\n        AS --&gt; IOR\n\n        VS --&gt; OA\n        PS --&gt; OA\n\n        IPG --&gt; PG\n        IOR --&gt; ORDERR\n    end\n\n    subgraph LEGEND[&quot;Legend&quot;]\n        direction LR\n        LC[&quot;Changed&quot;]:::lChanged\n        LA[&quot;Affected&quot;]:::lAffected\n        LU[&quot;Unchanged&quot;]:::lUnchanged\n    end<\/code><\/pre>\n\n\n\n<p><strong>Red<\/strong> \u2014 must change to implement the requirement. <strong>Orange<\/strong> \u2014 potentially affected depending on whether fraud status gates payment authorization. The change is functionally local but structurally diffuse, because the shared aggregate connects everything that touches it.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">In the VBD System<\/h3>\n\n\n\n<p>The <strong>RulesAccessor<\/strong> gains a query for fraud rules. The <strong>ValidationEngine<\/strong> gains the logic to evaluate them. The rules themselves live in the Rules Store, which already exists behind the accessor boundary.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">flowchart TB\n    classDef changed fill:#d93025,color:#ffffff,stroke:#b31412,stroke-width:2px;\n    classDef unchanged fill:#f1f3f4,color:#9aa0a6,stroke:#dadce0,stroke-width:1px;\n    classDef unchangedExt fill:#f8f9fa,color:#c5c8cb,stroke:#ebebeb,stroke-width:1px;\n    classDef lChanged fill:#d93025,color:#ffffff,stroke:#b31412,stroke-width:2px,font-size:11px;\n    classDef lUnchanged fill:#f1f3f4,color:#9aa0a6,stroke:#dadce0,stroke-width:1px,font-size:11px;\n\n    subgraph APP[&quot;Order Processing Application&quot;]\n        direction TB\n\n        OM[&quot;OrderManager&quot;]:::unchanged\n\n        VE[&quot;ValidationEngine&quot;]:::changed\n        PE[&quot;PricingEngine&quot;]:::unchanged\n\n        PM[&quot;PaymentManager&quot;]:::unchanged\n        NM[&quot;NotificationManager&quot;]:::unchanged\n\n        IA[&quot;ItemAccessor&quot;]:::unchanged\n        RA[&quot;RulesAccessor&quot;]:::changed\n        PRA[&quot;PriceAccessor&quot;]:::unchanged\n        OA[&quot;OrderAccessor&quot;]:::unchanged\n    end\n\n    subgraph EXT[&quot;External Systems&quot;]\n        direction LR\n        IDS[&quot;Item Data Source&quot;]:::unchangedExt\n        RDS[&quot;Rules Store&quot;]:::unchangedExt\n        PDS[&quot;Pricing Data Source&quot;]:::unchangedExt\n        ODS[&quot;Order Store&quot;]:::unchangedExt\n        PG[&quot;Payment Provider&quot;]:::unchangedExt\n        NS[&quot;Notification Service&quot;]:::unchangedExt\n    end\n\n    OM --&gt; VE\n    VE --&gt; IA\n    VE --&gt; RA\n\n    OM --&gt; PE\n    PE --&gt; PRA\n\n    OM --&gt; OA\n\n    OM -.-&gt;|async| PM\n    OM -.-&gt;|async| NM\n\n    IA --&gt; IDS\n    RA --&gt; RDS\n    PRA --&gt; PDS\n    OA --&gt; ODS\n    PM --&gt; PG\n    NM --&gt; NS\n\n    subgraph LEGEND[&quot;Legend&quot;]\n        direction LR\n        LC[&quot;Changed&quot;]:::lChanged\n        LU[&quot;Unchanged&quot;]:::lUnchanged\n    end<\/code><\/pre>\n\n\n\n<p>Two components change. Nothing else is aware a new rule exists.<\/p>\n\n\n\n<p><strong>OrderManager<\/strong> does not know a new rule exists. <strong>PricingEngine<\/strong> is untouched. <strong>PaymentManager<\/strong> is untouched. The change is contained to the component that owns validation behavior \u2014 because that is what the boundary was designed to isolate.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Same Requirement, Side by Side<\/h3>\n\n\n\n<figure class=\"wp-block-table hf-iso-table\"><table><thead><tr><th>Component<\/th><th>Clean Architecture<\/th><th>VBD<\/th><\/tr><\/thead><tbody><tr><td>Application Service \/ Manager<\/td><td>Changes \u2014 must handle fraud result<\/td><td>No change<\/td><\/tr><tr><td>Shared Domain Model \/ Aggregate<\/td><td>Changes \u2014 must carry fraud status<\/td><td>Does not exist as shared model<\/td><\/tr><tr><td>Validation logic<\/td><td>Changes<\/td><td>Changes (ValidationEngine only)<\/td><\/tr><tr><td>Rules data access<\/td><td>New or modified (on aggregate)<\/td><td>Changes (RulesAccessor only)<\/td><\/tr><tr><td>Payment path<\/td><td>Potentially affected<\/td><td>No change<\/td><\/tr><tr><td>Pricing logic<\/td><td>Untouched but coupled via aggregate<\/td><td>Untouched and structurally isolated<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Now consider what happens if the fraud check is slow. Engines and Resource Accessors are always called synchronously by their consuming Manager \u2014 async behavior is a Manager-level concern. The solution is not a new Manager. It is a new operation on the OrderManager itself: a validation entry point at the API surface that accepts the same order object. The OrderManager runs the ValidationEngine and RulesAccessor synchronously, then emits an event with the result. The OrderManager listens to its own events and picks up from there \u2014 continuing to process if validation passed, or surfacing the issue if it did not.<\/p><p>The client submits and waits. If something fails, they receive an event. If everything processes normally, they are none the wiser. The internal coordination is invisible to them \u2014 and invisible to every other component in the system.<\/p><p>In the clean architecture system, the same change requires restructuring how the application service coordinates its calls \u2014 the shared aggregate is the coordination point, so any change to when or how results flow through it touches the same components that already changed.<\/p>\n\n\n\n<figure class=\"wp-block-pullquote\" style=\"font-size:1rem\"><blockquote><p>If the rules live behind an accessor, they are already data. What if the ValidationEngine never needed to change at all \u2014 because the rules were configuration loaded at runtime? The boundary does not just contain change. It creates the conditions under which some changes stop being code changes entirely.<\/p><\/blockquote><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Why This Matters<\/h2>\n\n\n\n<p>The difference between these two designs is not stylistic. It is behavioral under change.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>In the first system, change propagates through shared orchestration<\/li>\n<li>In the second, change is localized to its volatility boundary<\/li>\n<li>In the first, use cases are bundled together<\/li>\n<li>In the second, use cases remain independent<\/li>\n<\/ul>\n\n\n\n<p>Volatility-Based Decomposition does not eliminate change. It ensures that change behaves predictably.<\/p>\n\n\n\n<p>That is the difference between a system that evolves and one that resists evolution.<\/p>\n\n\n\n<p>That is the point.<\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>Volatility-Based Decomposition begins with a simple premise: systems should be organized around how they change. Most architectural patterns organize code by technical role. Controllers live in one place, services in another, repositories somewhere else. This produces a clear taxonomy, but it does not explain how change propagates through the system. When business rules evolve, workflows &#8230; <a title=\"VBD in 10 Minutes: Organizing Code by Change\" class=\"read-more\" href=\"https:\/\/harmonic-framework.com\/es\/vbd-in-10-minutes-organizing-code-by-change\/\" aria-label=\"Read more about VBD in 10 Minutes: Organizing Code by Change\">Read more<\/a><\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_uag_custom_page_level_css":"","footnotes":""},"categories":[3],"tags":[],"methodology":[],"class_list":["post-183","post","type-post","status-publish","format-standard","hentry","category-tutorials"],"uagb_featured_image_src":{"full":false,"thumbnail":false,"medium":false,"medium_large":false,"large":false,"1536x1536":false,"2048x2048":false,"trp-custom-language-flag":false,"post-thumbnail":false,"hf-card":false,"hf-hero":false},"uagb_author_info":{"display_name":"William Christopher Anderson","author_link":"https:\/\/harmonic-framework.com\/es\/author\/admin\/"},"uagb_comment_info":0,"uagb_excerpt":"Volatility-Based Decomposition begins with a simple premise: systems should be organized around how they change. Most architectural patterns organize code by technical role. Controllers live in one place, services in another, repositories somewhere else. This produces a clear taxonomy, but it does not explain how change propagates through the system. When business rules evolve, workflows&hellip;","_links":{"self":[{"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/posts\/183","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/comments?post=183"}],"version-history":[{"count":21,"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/posts\/183\/revisions"}],"predecessor-version":[{"id":650,"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/posts\/183\/revisions\/650"}],"wp:attachment":[{"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/media?parent=183"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/categories?post=183"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/tags?post=183"},{"taxonomy":"methodology","embeddable":true,"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/methodology?post=183"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}