<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://antonello.provenza.no/feed.xml" rel="self" type="application/atom+xml" /><link href="https://antonello.provenza.no/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-05-14T21:25:55+00:00</updated><id>https://antonello.provenza.no/feed.xml</id><title type="html">Antonello@Provenza.no</title><subtitle>Technology, architecture and .NET</subtitle><author><name>Antonello Provenzano</name></author><entry xml:lang="en"><title type="html">Domain Events in .NET: A DDD Perspective</title><link href="https://antonello.provenza.no/architecture/ddd-domain-events-in-dotnet/" rel="alternate" type="text/html" title="Domain Events in .NET: A DDD Perspective" /><published>2026-05-14T00:00:00+00:00</published><updated>2026-05-14T00:00:00+00:00</updated><id>https://antonello.provenza.no/architecture/ddd-domain-events-in-dotnet</id><content type="html" xml:base="https://antonello.provenza.no/architecture/ddd-domain-events-in-dotnet/"><![CDATA[<p>Domain-Driven Design gave us a vocabulary that matters: <em>bounded contexts</em>, <em>aggregates</em>, <em>ubiquitous language</em>. But few of its concepts are as architecturally powerful — or as frequently misused — as <strong>domain events</strong>.</p>

<p>In most codebases I have reviewed, events are treated as a messaging detail. You pick a broker, wire up a serializer, and call it done. The architecture is shaped by the transport, not by the domain. This is exactly backwards.</p>

<h2 id="what-a-domain-event-actually-is">What a Domain Event Actually Is</h2>

<p>Eric Evans defined it plainly</p>

<blockquote>
  <p>A domain event is something that <em>happened</em> inside the domain — something meaningful to domain experts, worth recording, and worth broadcasting.</p>
</blockquote>

<p>That last word is the key. Not <em>sending</em>. Not <em>commanding</em>. <strong>Broadcasting</strong> — because the producing context does not know, and should not know, who is listening.</p>

<p>Three properties follow from this definition:</p>

<ul>
  <li><strong>Immutability</strong> — an event is a fact about the past. It cannot be rejected. <code class="language-plaintext highlighter-rouge">OrderPlaced</code> already happened; there is nothing left to accept or refuse.</li>
  <li><strong>Named in the ubiquitous language</strong> — <code class="language-plaintext highlighter-rouge">OrderPlaced</code>, <code class="language-plaintext highlighter-rouge">InvoiceIssued</code>, <code class="language-plaintext highlighter-rouge">UserRegistered</code>. Not <code class="language-plaintext highlighter-rouge">MessageSentToQueue</code>. Domain experts should recognize the name immediately.</li>
  <li><strong>Temporal decoupling</strong> — consumers process events at their own pace. The producing bounded context does not wait, does not care, and does not retry on their behalf.</li>
</ul>

<p>This is different from a command. A command is a request: <em>do this</em>. It can be rejected. An event is a statement of fact: <em>this happened</em>. It cannot.</p>

<h2 id="why-events-matter-architecturally">Why Events Matter Architecturally</h2>

<p><img src="/assets/img/2026-05-14-ddd-domain-events-in-dotnet/domain-events-architecture.png" alt="One producer, many consumers — no direct coupling between bounded contexts" class="align-center" /></p>

<p>Bounded contexts need to share information without becoming tightly coupled. This is one of the hardest problems in enterprise architecture, and it is where events earn their place.</p>

<p>Without events, the typical solution is direct invocation: one service calls another, either synchronously over HTTP or through a shared library. Both approaches create coupling. The caller must know the address of the callee. Changes to one can break the other. Deployments become coordinated.</p>

<p>With events, the producing context simply announces what happened. Any other context that cares can subscribe. The relationship is inverted: consumers depend on the producer’s contract, not on its implementation. Adding a new consumer requires no change to the producer.</p>

<p>This is how bounded contexts integrate cleanly. It is also what enables the kind of architecture that can scale both technically and organizationally — teams can work on their contexts independently, as long as they agree on the event contracts.</p>

<h2 id="domain-events-vs-integration-events">Domain Events vs. Integration Events</h2>

<p>A distinction that often gets lost in practice: domain events and integration events are not the same thing.</p>

<p><strong>Domain events</strong> are exchanged <em>within</em> a bounded context, between aggregates. They tend to be fine-grained and carry only the delta — the piece of information that changed. They do not need to be serialized to a broker. They can be dispatched in-process, within the same unit of work.</p>

<p><strong>Integration events</strong> cross context boundaries. They need to be serialized, transported, and versioned. They typically carry more data, because the receiving context cannot reach into the producing context’s storage to fetch the rest. They are the contracts between teams.</p>

<p>Confusing the two leads to either over-coupling (integration events used inside a context, carrying unnecessary data dependencies) or under-specification (domain events used across boundaries, without proper versioning or schema governance).</p>

<h2 id="modeling-events-as-first-class-contracts">Modeling Events as First-Class Contracts</h2>

<p>If integration events are contracts between teams, they deserve the same governance as any other public API. That means:</p>

<ul>
  <li>A machine-readable schema, not just a code example.</li>
  <li>Versioning, with explicit rules about breaking changes.</li>
  <li>A discovery mechanism, so consumers can find what events are available.</li>
</ul>

<p>This is where most teams struggle. REST APIs have <a href="https://www.openapis.org/">OpenAPI</a>. Events get a Confluence page that is out of date within a week.</p>

<p>The architectural answer to this is to treat event schemas the same way you treat API specifications: version-controlled, machine-readable, and published alongside the producing service.</p>

<h2 id="deveel-events-a-net-implementation">Deveel Events: A .NET Implementation</h2>

<p><a href="https://events.deveel.org">Deveel Events</a> (<a href="https://github.com/deveel/deveel.events">GitHub</a>) is a lightweight .NET framework built on top of the <a href="https://cloudevents.io/">CloudEvents</a> standard. It focuses on the <strong>publishing side</strong> of domain events — the part that most teams rewrite from scratch every time they start a new service.</p>

<p>What makes it architecturally interesting is its scope. It does not try to be a full service-bus runtime. It does not dictate how you model aggregates or build read models. It solves one problem well: broadcasting domain events from a bounded context to any number of downstream consumers, through a transport-agnostic layer.</p>

<h3 id="annotating-an-event-contract">Annotating an Event Contract</h3>

<p>The starting point is a data class annotated with <code class="language-plaintext highlighter-rouge">[Event]</code>. This is your contract:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.ComponentModel.DataAnnotations</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Deveel.Events</span><span class="p">;</span>

<span class="p">[</span><span class="nf">Event</span><span class="p">(</span><span class="s">"order.placed"</span><span class="p">,</span> <span class="s">"1.0"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">OrderPlacedData</span>
<span class="p">{</span>
    <span class="p">[</span><span class="n">Required</span><span class="p">]</span>
    <span class="k">public</span> <span class="n">Guid</span> <span class="n">OrderId</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

    <span class="p">[</span><span class="n">Required</span><span class="p">]</span>
    <span class="p">[</span><span class="nf">Range</span><span class="p">(</span><span class="m">0.01</span><span class="p">,</span> <span class="m">1</span><span class="n">_000_000</span><span class="p">.</span><span class="m">0</span><span class="p">)]</span>
    <span class="k">public</span> <span class="kt">decimal</span> <span class="n">Amount</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

    <span class="p">[</span><span class="n">Required</span><span class="p">]</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Currency</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="k">default</span><span class="p">!;</span>

    <span class="k">public</span> <span class="kt">string</span><span class="p">?</span> <span class="n">Notes</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">[Event]</code> attribute captures the event type string (<code class="language-plaintext highlighter-rouge">order.placed</code>) and its version (<code class="language-plaintext highlighter-rouge">1.0</code>). These values become part of the CloudEvents envelope, giving every published event a stable, identifiable type.</p>

<h3 id="registering-the-publisher">Registering the Publisher</h3>

<p>Registration lives in the DI setup, separate from the domain model:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">Deveel.Events</span><span class="p">;</span>

<span class="kt">var</span> <span class="n">builder</span> <span class="p">=</span> <span class="n">WebApplication</span><span class="p">.</span><span class="nf">CreateBuilder</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>

<span class="n">builder</span><span class="p">.</span><span class="n">Services</span>
    <span class="p">.</span><span class="nf">AddEventPublisher</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
    <span class="p">{</span>
        <span class="n">options</span><span class="p">.</span><span class="n">Source</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">"https://myapp.example.com"</span><span class="p">);</span>
    <span class="p">})</span>
    <span class="p">.</span><span class="nf">AddServiceBus</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
    <span class="p">{</span>
        <span class="n">options</span><span class="p">.</span><span class="n">ConnectionString</span> <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">[</span><span class="s">"ServiceBus:ConnectionString"</span><span class="p">]!;</span>
        <span class="n">options</span><span class="p">.</span><span class="n">QueueName</span>        <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">[</span><span class="s">"ServiceBus:QueueName"</span><span class="p">]!;</span>
    <span class="p">});</span>
</code></pre></div></div>

<p>The publisher is transport-agnostic. Swapping <em>Azure Service Bus</em> for <em>RabbitMQ</em> means changing the channel registration, not the domain code.</p>

<h3 id="publishing-from-a-domain-service">Publishing from a Domain Service</h3>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">Deveel.Events</span><span class="p">;</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">OrderService</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">EventPublisher</span> <span class="n">_publisher</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">OrderService</span><span class="p">(</span><span class="n">EventPublisher</span> <span class="n">publisher</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_publisher</span> <span class="p">=</span> <span class="n">publisher</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">PlaceOrderAsync</span><span class="p">(</span><span class="n">Guid</span> <span class="n">orderId</span><span class="p">,</span> <span class="kt">decimal</span> <span class="n">amount</span><span class="p">,</span> <span class="kt">string</span> <span class="n">currency</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// ... domain logic ...</span>

        <span class="kt">var</span> <span class="n">data</span> <span class="p">=</span> <span class="k">new</span> <span class="n">OrderPlacedData</span>
        <span class="p">{</span>
            <span class="n">OrderId</span>  <span class="p">=</span> <span class="n">orderId</span><span class="p">,</span>
            <span class="n">Amount</span>   <span class="p">=</span> <span class="n">amount</span><span class="p">,</span>
            <span class="n">Currency</span> <span class="p">=</span> <span class="n">currency</span>
        <span class="p">};</span>

        <span class="k">await</span> <span class="n">_publisher</span><span class="p">.</span><span class="nf">PublishAsync</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The domain service does not reference the broker. It does not know whether the event will go to <em>Azure Service Bus</em>, <em>RabbitMQ</em>, or an <em>HTTP webhook</em>. That is <strong>a deployment decision, not a domain one</strong>.</p>

<h3 id="schema-governance">Schema Governance</h3>

<p>One of the most valuable parts of the framework is its schema support. The <code class="language-plaintext highlighter-rouge">Deveel.Events.Schema</code> package derives a schema from annotated data classes and exports it as JSON Schema, YAML, or an <a href="https://www.asyncapi.com/">AsyncAPI</a> 2.x document.</p>

<p>This is what gives integration events the same governance weight as REST APIs. Consumers get a machine-readable contract. Schema registries can validate payloads. Breaking changes can be detected automatically.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet add package Deveel.Events.Schema
dotnet add package Deveel.Events.Schema.AsyncApi
</code></pre></div></div>

<p>The <a href="https://www.asyncapi.com/">AsyncAPI</a> export is particularly useful: it produces a complete, machine-readable API specification for asynchronous messaging — the same role <a href="https://www.openapis.org/">OpenAPI</a> plays for REST. Tooling can generate documentation sites, client SDKs, and mock servers from it.</p>

<h2 id="the-architectural-principle">The Architectural Principle</h2>

<p>The framework mechanics are secondary. The principle matters more.</p>

<p>Domain events are contracts. They deserve the same discipline as any other public interface: versioning, schema governance, and explicit communication of breaking changes. They should be named by domain experts, not by the infrastructure team. They should be scoped to what actually happened, not to what the receiving system happens to need today.</p>

<p>When you treat events as first-class architectural citizens — and invest in the tooling to govern them — bounded contexts can evolve independently. Teams can work in parallel. The system can scale.</p>

<p>When you treat events as a messaging detail, you get a distributed monolith with extra latency.</p>

<p>The choice is made in design, not in deployment.</p>

<hr />

<p><em>Deveel Events documentation is at <a href="https://events.deveel.org">events.deveel.org</a>. Source code and issue tracker are on <a href="https://github.com/deveel/deveel.events">GitHub</a>.</em></p>

<p><strong>Disclosure:</strong> I am the author of the <a href="https://github.com/deveel/deveel.events">Deveel Events</a> project. The views expressed in this post reflect my own architectural perspective. You can find my other open-source work on <a href="https://github.com/deveel">GitHub</a>.</p>]]></content><author><name>Antonello Provenzano</name></author><category term="architecture" /><category term="domain-driven-design" /><category term="events" /><category term="dotnet" /><category term="architecture" /><category term="event-driven" /><summary type="html"><![CDATA[Domain events are a first-class concept in DDD, yet they are often reduced to technical plumbing. This post explores what they mean architecturally and how Deveel Events brings them to life in .NET.]]></summary></entry><entry xml:lang="en"><title type="html">Architects Need Peers</title><link href="https://antonello.provenza.no/architecture/architects-need-peers/" rel="alternate" type="text/html" title="Architects Need Peers" /><published>2026-05-08T00:00:00+00:00</published><updated>2026-05-08T00:00:00+00:00</updated><id>https://antonello.provenza.no/architecture/architects-need-peers</id><content type="html" xml:base="https://antonello.provenza.no/architecture/architects-need-peers/"><![CDATA[<p>There is a romantic image of the architect as a solitary thinker, sitting in front of a whiteboard, connecting boxes and arrows in silence, and then walking into the room with a complete vision that everyone else should simply execute. It looks elegant in stories, but in real organizations it is one of the fastest ways to build fragile architecture with confident documentation.</p>

<p>Even when the final accountability is mine, and even when I know I will be the one defending the design in front of leadership, delivery teams, security, and operations, I do not want to design alone. I want peers around me who can challenge my assumptions before production does, because production is the most expensive and least forgiving reviewer you can find.</p>

<h2 id="ownership-is-not-solitude">Ownership is not solitude</h2>

<p><img src="/assets/img/2026-05-08-architects-need-peers/architect-with-peers-reviewing-design.jpg" alt="Architect peers reviewing a system design on a whiteboard" class="align-center" /></p>

<p>One misunderstanding I see often is the idea that ownership means isolation. It does not. Ownership means I make the call when a decision is needed, I explain the trade-offs clearly, and I take responsibility for the consequences. Isolation, on the other hand, means I protect my design from criticism until it is too late to change it cheaply.</p>

<p>A strong architect should be decisive, but never unchallenged. If every architecture conversation ends with “the architect said so,” then we are not doing architecture, we are doing hierarchy.</p>

<p>When hierarchy becomes too heavy, people outside the architect role often stop challenging openly, even when they clearly see a flaw. Not because they agree, but because they are calculating the social cost: if they push too hard, will they be seen as difficult, disrespectful, or as blockers to delivery? That silence is easy to misread as alignment.</p>

<p>The dangerous part comes later. The criticism that was not voiced in the room does not disappear; it moves underground into implementation choices. Teams start making quiet deviations, defensive workarounds, and side decisions behind the curtain to protect delivery from a design they do not trust. At that point, the architecture may still look coherent in documents, but it has already started to fragment in practice.</p>

<p>The role is not to be the loudest voice in the room; the role is to integrate the best evidence from people who see different corners of the system.</p>

<h2 id="peers-are-critical-sparring-partners">Peers are critical sparring partners</h2>

<p>An architect peer is not there to agree politely and move on. A useful peer is someone who asks uncomfortable questions, spots hidden coupling, questions optimistic assumptions, and forces clarity where language has become too abstract.</p>

<p>In practice, this kind of sparring catches exactly the mistakes that are easy to miss when you are deep in your own mental model: integration points that look simple but are operationally heavy, data ownership boundaries that seem obvious but collapse under real usage, security constraints that appear late, and migration plans that are technically possible but organizationally unrealistic.</p>

<p>The value of peer challenge is not just technical quality. It also improves legitimacy. When teams know a design has survived serious internal scrutiny by competent peers, they trust the architecture process more, and they engage earlier with better feedback instead of resisting later when timelines are already under pressure.</p>

<h2 id="what-this-looks-like-in-real-work">What this looks like in real work</h2>

<p>You do not need heavy governance theater to make peer contribution real. You need regular habits.</p>

<p>I have seen good results when architects adopt a small set of simple rituals: short architecture design reviews with explicit decision records, pre-mortem sessions where peers try to break the design on purpose, and periodic checkpoints during implementation where the original assumptions are tested against reality rather than protected as doctrine.</p>

<p>Another important habit is to separate criticism of the design from criticism of the person. If peer review becomes political, everyone becomes defensive and no one learns. If peer review is framed as a shared responsibility to reduce delivery and operational risk, then challenge becomes a professional service, not a personal attack.</p>

<h2 id="the-architecture-gets-better-and-so-does-the-architect">The architecture gets better, and so does the architect</h2>

<p>The real outcome of peer contribution is not only fewer defects or fewer late surprises, even though those benefits are substantial. The deeper benefit is that the architect grows in judgment, because repeated exposure to strong counterarguments improves decision quality over time.</p>

<p>An architect who never gets challenged usually becomes rigid. An architect who invites challenge becomes sharper, calmer under pressure, and more credible when a difficult decision must finally be made.</p>

<p>So yes, one architect may hold the final responsibility, sign off the decision, and stand behind it across the organization. But architecture should still be forged with peers, tested by critical sparring, and strengthened by collective intelligence, because that is how you design systems that can survive contact with reality.</p>]]></content><author><name>Antonello Provenzano</name></author><category term="architecture" /><category term="architecture" /><category term="collaboration" /><category term="leadership" /><category term="culture" /><summary type="html"><![CDATA[Architecture accountability can sit with one person, but architecture quality is always a team result shaped by peers who challenge assumptions and expose blind spots.]]></summary></entry><entry xml:lang="en"><title type="html">Architectural Patterns in Structured Prompt-Driven Development</title><link href="https://antonello.provenza.no/architecture/structured-prompt-driven-development-patterns/" rel="alternate" type="text/html" title="Architectural Patterns in Structured Prompt-Driven Development" /><published>2026-05-03T00:00:00+00:00</published><updated>2026-05-03T00:00:00+00:00</updated><id>https://antonello.provenza.no/architecture/structured-prompt-driven-development-patterns</id><content type="html" xml:base="https://antonello.provenza.no/architecture/structured-prompt-driven-development-patterns/"><![CDATA[<p>I recently read the article by Wei Zhang and Jessie Jie Xia about <a href="https://martinfowler.com/articles/structured-prompt-driven/">Structured Prompt-Driven Development (SPDD)</a>, edited by <a href="https://martinfowler.com">Martin Fowler</a>, as an architecture signal, not as a prompt-writing guide. What caught my attention is not the technique itself, but the underlying shift in responsibility: moving from individual prompt skill to system-level design.</p>

<p>My opinion is straightforward. If we keep treating prompting as a personal craft, we will keep getting local wins and organizational fragility. If we treat it as architecture, we can aim for repeatability, accountability, and controlled evolution. In my experience, teams rarely fail because a model is incapable; they fail because there is no shared architecture around how that model is used. This is exactly why Fowler and his co-authors’ framing matters: they are not proposing prompt cleverness, they are proposing delivery discipline.</p>

<p><img src="/assets/img/2026-05-03-structured-prompt-driven-development-patterns/structured-prompt-architecture.svg" alt="Structured Prompt-Driven Development architecture flow" class="align-center" /></p>

<h2 id="from-prompt-skill-to-delivery-architecture">From prompt skill to delivery architecture</h2>

<p>I have seen a recurring pattern in AI-assisted development initiatives. A small group of engineers gets very good results with ad-hoc prompting, the rest of the organization tries to imitate those results, and leadership assumes it has found a scalable operating model. It usually has not. One contribution I appreciate in Zhang’s article is that his team makes this distinction explicit: individual effectiveness is not the same as organizational reliability.</p>

<p>At that point, familiar architectural problems emerge. Different people use different implicit interfaces for the same task. Valuable knowledge ends up in private chats rather than reusable assets. Reviews focus on generated code while ignoring the generation conditions. Sensitive context is copied around with no clear policy boundary. To me, this looks very similar to a distributed monolith in integration architecture: productive in short bursts, expensive and risky over time.</p>

<p>This is why I prefer to think in terms of prompt assets rather than prompts. A reusable prompt module should be narrow in intent, explicit in assumptions, constrained by policy, and stable in output shape. I see no meaningful difference between this and how we design internal APIs or shared platform libraries. It is the same engineering discipline applied to a new interface. In that sense, I read Zhang’s structure as a software architecture concern disguised as AI guidance.</p>

<p>When I imagine a concrete case, such as ADR generation, I do not want each engineer inventing structure from scratch. I want a governed module that always returns context, options, trade-offs, recommendations, and risks. That consistency does not reduce creativity; it reduces ambiguity and makes architectural reasoning comparable across teams.</p>

<h2 id="the-boundary-problem-most-teams-underestimate">The boundary problem most teams underestimate</h2>

<p>The part I consider most underestimated is context. Context injection is not a convenience feature; it is a data architecture concern with governance implications. Zhang repeatedly emphasizes structure around inputs and expected outputs, and I think that is where many teams still underinvest.</p>

<p>I find it useful to frame context as an envelope with clear boundaries: what is required to do the work, what is conditionally useful, and what is restricted unless transformed or explicitly approved. Without that framing, teams usually end up with both compliance exposure and reproducibility problems. The output quality also becomes unstable because each run is shaped by arbitrary context choices.</p>

<p>A backlog decomposition assistant is a good example. I would allow domain glossary, service boundaries, and non-functional constraints, because those improve relevance. I would block production secrets, raw customer transcripts, and unapproved roadmap material, because those create risk with no proportional value. Architecture, in this scenario, is the mechanism that maximizes useful flow while controlling coupling and exposure.</p>

<h2 id="reliability-requires-deterministic-controls">Reliability requires deterministic controls</h2>

<p>The other point I feel strongly about is evaluation. I do not trust generated output without gates, not because models are inherently poor, but because architecture should always assume variance.</p>

<p>For me, structured prompt-driven workflows become credible only when deterministic controls are built into the pipeline: format checks, policy checks, architecture conformance checks, and mandatory human review for high-impact changes. Without these controls, teams can ship plausible regressions faster than before, which is the worst possible outcome of acceleration. This is another point where I align with Zhang’s broader engineering philosophy: fast feedback without quality control is not acceleration, it is deferred failure.</p>

<p>In legacy migration work, for instance, my minimum expectation is simple: the build must pass, the existing test suite must pass without reducing scope, forbidden dependencies must stay out, and architecture rules must still hold. If one of these conditions fails, the workflow should go back to refinement instead of moving toward merge. I consider this non-negotiable.</p>

<p>This is also why I prefer orchestration to one-shot prompting. Real delivery work has stages, dependencies, and feedback loops. A staged flow that frames constraints, proposes a plan, generates changes, validates them, and documents rationale is more diagnosable and easier to improve than a single opaque interaction. When quality drops, I can see where it dropped and fix that stage.</p>

<p>From a governance perspective, I think a federated model works best: platform teams own shared modules and gates, product teams own domain context, and architecture or security functions own guardrails and exceptions. That balance avoids both central bottlenecks and unmanaged fragmentation.</p>

<p>Yes, this approach adds overhead. It requires asset lifecycle management, context governance, gate automation, and new skills in failure analysis. I accept that trade-off for the same reason I accept the cost of CI/CD and API governance: reliability is never free, and pretending otherwise is usually how technical debt is born.</p>

<p>I do not see Structured Prompt-Driven Development as another methodology to memorize. I see it as a correction toward architectural maturity. The meaningful shift, in my mind, is moving from <em>“I can get good results with prompts”</em> to <em>“we can deliver reliable outcomes with a governed system.”</em> Reading Zhang’s article through that lens helped me clarify my own position: the long-term value is not in better prompts, but in better architecture around prompt-driven work. Once that shift happens, the priorities become obvious: design boundaries, define contracts, instrument workflows, and enforce quality. That is architecture work, and I believe it deserves the same seriousness as any other production-critical concern.</p>]]></content><author><name>Antonello Provenzano</name></author><category term="architecture" /><category term="architecture" /><category term="ai" /><category term="llm" /><category term="development" /><category term="governance" /><summary type="html"><![CDATA[Structured Prompt-Driven Development introduces reusable architectural patterns for building reliable LLM-enabled delivery workflows across teams.]]></summary></entry><entry xml:lang="en"><title type="html">The Objectivity of Architecture</title><link href="https://antonello.provenza.no/architecture/the-objectivity-of-architecture/" rel="alternate" type="text/html" title="The Objectivity of Architecture" /><published>2026-02-15T00:00:00+00:00</published><updated>2026-02-15T00:00:00+00:00</updated><id>https://antonello.provenza.no/architecture/the-objectivity-of-architecture</id><content type="html" xml:base="https://antonello.provenza.no/architecture/the-objectivity-of-architecture/"><![CDATA[<p><img src="/assets/img/2026-02-15-the-objectivity-of-architecture/objective-thinking-subjective-bias.png" alt="Objective Thinking vs Subjective Bias" /></p>

<p>Last week, I had a meeting with a supplier to present an architectural blueprint I had prepared, with the goal of assessing whether their system could support it.</p>

<p>After a brief presentation, we moved into a bilateral discussion. A young engineer on their side challenged my assumptions, and within fifteen minutes I was convinced he was right. I had to revise — fortunately not radically — the processes and integrations I had originally envisioned.</p>

<p>When I openly acknowledged that he was right, both the supplier’s team and my own colleagues seemed genuinely surprised. Some even tried, half-jokingly, to talk me out of changing my original design, like if the young engineer acted in disrespect. In that moment, I had another realization: many people take it for granted that an Architect should possess complete knowledge of everything within the scope of the architecture they design — to the point of defying the internal logic of systems themselves and arrogantly imposing a personal vision on top of reality.</p>

<p>I firmly believe that an Architect who works this way is preparing for disaster. If you do not study the terrain, understand the materials, or acknowledge the skills of the people involved, you are not designing an architecture — you are performing a vanity exercise. And vanity-driven architectures eventually collapse, causing harm to others and often significant financial damage.</p>

<p>For some, being an Architect is reduced to a symbolic milestone in career progression: a title to wield against others, a perceived badge of superiority, perhaps even a justification for a higher salary. (I can personally debunk that myth—I have been an Enterprise Architect while earning less than several senior developers.) The real cost of this mindset, however, is the gradual loss of objectivity and rational thinking.</p>

<p>This reminds me of a situation where I had a prolonged conflict with a colleague. We argued for weeks, exchanged harsh emails, and strongly disagreed on many issues. Yet, during a public architectural discussion, I openly defended his position on a topic where I believed he was right. Later, he reached out privately to thank me for being objective and for not letting personal differences interfere. That reaction surprised me—almost as if objectivity were optional, even when the alternative could risk hundreds of thousands, if not millions, in damages. Silencing one’s ego is difficult, in architecture as in any other discipline, but the psychological effort pays off in the long run.</p>

<p>One aspect that is often misunderstood from the outside is that an Architect is not a Subject Matter Expert. Architects are not expected to know everything about every system, technology, process, or organizational nuance within the scope of their architecture. Sometimes, even Architects themselves forget this, falling into the trap of believing they can independently interpret business, legal, technical, and organizational concerns without relying on experts in those domains.</p>

<p>Naturally, everyone brings personal interests and professional or academic backgrounds that lead to deeper knowledge in certain areas. But an Architect must remain acutely aware of the limits of their expertise—and be humble enough to listen, learn, and integrate the insights of true experts. Objectivity in architecture is not about knowing everything; it is about knowing how to synthesize reality, evidence, and expertise into coherent and sustainable decisions.</p>]]></content><author><name>Antonello Provenzano</name></author><category term="architecture" /><category term="culture" /><category term="architecture" /><summary type="html"><![CDATA[Architects should always prioritize the objective aspects of architecture, rather than subjective opinions, preferences or assumptions not based on evidences or facts.]]></summary></entry><entry xml:lang="en"><title type="html">Refactoring Applications with Domain-Driven Design and Fig Strangler Pattern</title><link href="https://antonello.provenza.no/architecture/refactoring-with-domain-driven-design-and-fig-strangler/" rel="alternate" type="text/html" title="Refactoring Applications with Domain-Driven Design and Fig Strangler Pattern" /><published>2024-01-08T00:00:00+00:00</published><updated>2024-01-08T00:00:00+00:00</updated><id>https://antonello.provenza.no/architecture/refactoring-with-domain-driven-design-and-fig-strangler</id><content type="html" xml:base="https://antonello.provenza.no/architecture/refactoring-with-domain-driven-design-and-fig-strangler/"><![CDATA[<p><img src="/assets/img/2024-01-08-refactoring-with-strangler-fig/developer-writing-code-laptop.jpg" alt="Developer Writing Code" /></p>

<p>Dealing with legacy code is a common task for developers, and in most of the cases it is not a pleasant one, since they didn’t participate in the design of the system, if any design was done at all. Finding yourself in such situation might require that you will engage in some analysis and studies to refactor a legacy application using <a href="https://www.amazon.com/gp/product/0321125215/">Domain-Driven Design (DDD)</a> concepts, and possibly apply the <a href="https://martinfowler.com/bliki/StranglerFigApplication.html">Strangler Fig Pattern</a> to migrate from a monolithic application to a microservices architecture.</p>

<p>Embarking in such a journey is not to be taken lightly, and it should be sponsored by the organization and the management you belong to, because it will require a lot of resources and time, and it will be a long-term project: as such, the drivers for this effort shouldn’t be just technical or the desire to use the latest technologies, but the need to improve the business and the organization.</p>

<p>I would recommend some outcomes to argument in favor of this effort:</p>

<ul>
  <li><strong>Adoption of New Technologies</strong>: the legacy application is probably using old technologies, and it is not possible to upgrade them without breaking the application. The new application will be built using the latest technologies, and it will be easier to upgrade them in the future.</li>
  <li><strong>Improve the Readability of the Codebase</strong>: the legacy application is probably hard to understand and maintain, especially for newcomers, who would need a lot of time to get familiar with the codebase, resulting in a slow onboarding process. The new application will be easier to understand and maintain, and it will be easier to onboard new developers.</li>
  <li><strong>Improve the Testability of the Codebase</strong>: the legacy application is probably hard to test, and it is not possible to write automated tests for it. The new application will be easier to test, and it will be possible to write automated tests for it, resulting in a more reliable application.</li>
  <li><strong>Improve the Performance of the Application</strong>: the legacy application is probably slow, and it is not possible to improve its performance without breaking it. The new application will be faster, and it will be possible to improve its performance in the future.</li>
  <li><strong>Improve the Scalability of the Application</strong>: the legacy application is probably not scalable, and it is not possible to scale its functions without employing a lot of time, while a new designon will be able to be scaled easily it in the future.</li>
</ul>

<h2 id="the-legacy-application">The Legacy Application</h2>

<p><img src="/assets/img/2024-01-08-refactoring-with-strangler-fig/Legacy-Monolith.png" alt="Legacy Application" /></p>

<p>Before the <em>mainstream</em> introduction of patterns and practices like DDD, SOLID, TDD, etc., developers used to write code in a procedural way, with a lot of code duplication, and with a lot of business logic in the UI or presentation (eg. APIs) layer. Furthermore, the lack of analysis of the business domain, the lack of definition of clear bounded contexts, and the lack of communication between developers and domain experts, resulted in a codebase that is hard to understand and maintain.</p>

<p>It is quite common in applications that have been written in such a manner to find the following issues:</p>

<ul>
  <li><strong>Lack of Separation of Concerns</strong>: the codebase is not organized in a way that allows to separate the business logic from the infrastructure, and the business logic is often mixed with the infrastructure code.</li>
  <li><strong>Lack of Abstraction</strong>: the codebase is not organized in a way that allows to abstract either the business logic, the data access, or the infrastructure, hindering the possibility to replace or scale them.</li>
  <li><strong>Incorrect Layering</strong>: presentation layers often contain business logic, and business logic, or even data access is operated in these topmost layers, making the code not reusable from other applications.</li>
</ul>

<h3 id="analysis-of-the-legacy-application">Analysis of the Legacy Application</h3>

<p>In most of the cases that require the refactoring of a legacy application, the codebase is not documented, and the developers who wrote it are not available anymore, so the only way to understand the application is to analyze the codebase.</p>

<p>Also, many times the choices of the developers who wrote the application are not clear, and it is not possible to understand why they made those choices: they might not have been informed or trained on the patterns and practices that are used to write maintainable code, or they might have been forced to use a specific technology or architecture by the management.</p>

<p>This analysis should be done by a team of developers, and it should be done in a way that is not disruptive for the business, so it should be done in parallel with the development of new features and bug fixes.</p>

<h2 id="definition-of-the-bounded-contexts">Definition of the Bounded Contexts</h2>

<p>Before the refactoring of the legacy application can start, it is necessary to define the <a href="https://martinfowler.com/bliki/BoundedContext.html">bounded contexts</a> of the business domain, and to define the relationships between them.</p>

<p>This aims to define a set of containments for entities, value objects, and aggregates that are logically part of a well-defined context, and that are related to each other, and eventually to define the relationships between these contexts.</p>

<p>For example, in a system that manages the orders of an e-commerce, the bounded contexts could be:</p>

<ul>
  <li><strong>Orders</strong>: The management of the orders places by the <em>Customer</em> of the e-commerce.</li>
  <li><strong>Customers</strong>: The management of the entities (individuals or organizations) that acquire products and services of the e-commerce, and it is related to the bounded context of the <em>Orders</em>.</li>
  <li><strong>Products</strong>: The products and services that are sold by the e-commerce, and it is related to the bounded context of the <em>Orders</em> and <em>Customers</em>.</li>
</ul>

<h3 id="definition-of-the-entities-value-objects-and-aggregates">Definition of the Entities, Value Objects, and Aggregates</h3>

<p>Once the bounded contexts have been defined, it is necessary to define the <em>entities</em>, <em>value objects</em>, and <em>aggregates</em> that are part of each bounded context, so that it is possible to define a coherent model of the business domain.</p>

<p>It is not uncommon that the legacy application analyzed has already some prototypes of entities, and even aggregates, but they are generally not defined in a way that is coherent with the bounded contexts, and they are not related to each other, or even that the developers who wrote the application didn’t know about the concept of <em>entities</em>, and they didn’t define the application objects coherently.</p>

<p>For example, in the bounded context of the <em>Orders</em>, the entities could be:</p>

<ul>
  <li><strong>Order</strong>: The entity that represents an order placed by a <em>Customer</em>.</li>
  <li><strong>OrderItem</strong>: The entity that represents an item of an order, and it is related to the <em>Order</em> and <em>Product</em> entities.</li>
</ul>

<p>While examples of value object could be:</p>

<ul>
  <li><strong>Amount</strong> - The value object that represents the amount of an order, and it is related to the <em>Order</em> entity, calculated as the sum of the amounts of the <em>OrderItem</em> entities.</li>
  <li><strong>Quantity</strong> - The value object that represents the quantity of an order item, and it is related to the <em>OrderItem</em> entity.</li>
</ul>

<p>In this case, the <em>Order</em> entity is to be considered also an <em>aggregate</em>, since it contains the <em>OrderItem</em> entities, and it is the only entity that can be accessed from the outside of the bounded context.</p>

<h3 id="domain-events-and-integration-events">Domain Events and Integration Events</h3>

<p>Once the entities have been defined, it is necessary to define the domain events that are related to them, and that are used to communicate the changes of the state of the entities to the other bounded contexts.</p>

<p>The typical approach to design domain events (those exchanged within the same domain) is through the methodology of the <em>delta events</em>, which consists in defining an event for each change of the state of an entity, and that contains the information about the change, not the new state of the entity.</p>

<p>In fact, this allows the minimization of the size of the information exchanged, that other components of the system might not need, and it allows to avoid the duplication of the information, that might be already available in the other components of the system.</p>

<p>These are not to be confused with the <em>integration events</em>, which are used to communicate the changes of the state of the entities to the other bounded contexts, and that contain the new state of the entity: they are richer and bigger, and they often contain the full information of the entity, and not just the information about the change.</p>

<p>Events play a fundamental role in the communication between the bounded contexts, to trigger the execution of the business logic of the other bounded contexts, and they are used to avoid the coupling between the bounded contexts, and to allow the scalability of the application.</p>

<h2 id="the-application-architecture">The Application Architecture</h2>

<p>It is quite often that the architecture of the legacy application is a monolithic one, with a single codebase that contains all the code of the application, and that is deployed as a single unit.</p>

<p>This architecture is not suitable for a complex organization that aims to scale fast, and provide <em>continuous delivery</em> of new features and bug fixes, because it is not possible to scale the application, and it is not possible to deploy new features and bug fixes without deploying the whole application.</p>

<p>Adopting an architecture of the application that is based on microservices, with a set of services that are deployed independently, and that communicate with each other using a messaging system, will allow the organization to scale the application, and to deploy new features and bug fixes without deploying the whole application.</p>

<p><strong>NOTE</strong>: Not in all cases this choice is the best one to take, since it might result in an exponential increase of the complexity of the organization, that might not be suitable for companies of just few people.</p>

<h2 id="the-strangler-fig-pattern">The Strangler Fig Pattern</h2>

<p><img src="/assets/img/2024-01-08-refactoring-with-strangler-fig/Strangler-Fig-Pattern.png" alt="Strangler Fig Pattern" /></p>

<p>The <a href="https://martinfowler.com/bliki/StranglerFigApplication.html">Strangler Fig Pattern</a> is a pattern that is used to migrate from a monolithic application to a microservices architecture, and it is based on the idea of a <em>strangler fig</em> tree, that grows on another tree, and it slowly strangles it, until it replaces it.</p>

<p>Adopting this pattern requires to create a new application that will replace the legacy application, and to migrate the features of the legacy application to the new application, until the legacy application is not used anymore.</p>

<p>This dynamic is typically required when the legacy application is not possible to be refactored, because it is highly coupled, and it is not possible to extract the features of the application in a way that they can be refactored independently.</p>

<h3 id="the-strangler-fig-pattern-in-practice">The Strangler Fig Pattern in Practice</h3>

<p>On a high level, the Strangler Fig Pattern can be implemented with a game of routing:</p>

<ol>
  <li>Create a new application that will replace the legacy one.</li>
  <li>Use an API Gateway to route the requests to the legacy application or the new one, based on the features and/or versions that have been migrated to the new application.</li>
  <li>Migrate the features of the legacy application to the new one, and update the API Gateway to route the requests to the new application.</li>
  <li>When all the features of the legacy application have been migrated to the new application, the legacy application can be decommissioned.</li>
</ol>

<p>This approach allows to migrate the features of the legacy application to the new one, without having to stop the development of new features and bug fixes, and without having to stop the deployment of new features and bug fixes.</p>

<p>It has to be clarified that this pattern doesn’t apply only to the migration from a monolithic application to a microservices architecture, but it can be used also to migrate from a legacy application to a new one, even if the architecture of the new application is not a microservices one: this could be the case when it is not possible to evolve the legacy application, and a new one has to be created.</p>

<h2 id="how-to-deal-with-the-human-factor">How to Deal with the Human Factor</h2>

<p><img src="/assets/img/2024-01-08-refactoring-with-strangler-fig/group-young-developers-working-together.jpg" alt="Developers Working Together" /></p>

<p>The refactoring of a legacy application is not just a technical task, but it is also a human one, because it requires to deal with the developers who wrote the legacy application, who might feel under attack, and they might not be happy to see their work being refactored.</p>

<p>It is important to involve the developers who wrote the legacy application in the refactoring process, and to make them understand that the refactoring is not a way to criticize their work, but it is a way to improve the business and the organization: good developers will understand this situation, and they might recognize they didn’t have the knowledge to write a better application, and they might be happy to learn new patterns and practices.</p>

<p>In most of the cases, the codebase of an application is the result of several years of development, by several developers, which was inherited by the current teams, who might have not been happy themselves to write code in that way, but they didn’t have the time to refactor it, because they were busy with the development of new features and bug fixes.</p>]]></content><author><name>Antonello Provenzano</name></author><category term="architecture" /><category term="domain-driven-design" /><category term="refactoring" /><category term="architecture" /><category term="patterns" /><summary type="html"><![CDATA[Migrating from a monolithic application to a microservices architecture using Domain-Driven Design might be complex and can be done in phases.]]></summary></entry><entry xml:lang="en"><title type="html">Webhooks Activate Asynchronous Integration Patterns</title><link href="https://antonello.provenza.no/open-source/events/webhooks-activate-asynchronous/" rel="alternate" type="text/html" title="Webhooks Activate Asynchronous Integration Patterns" /><published>2023-11-19T00:00:00+00:00</published><updated>2023-11-19T00:00:00+00:00</updated><id>https://antonello.provenza.no/open-source/events/webhooks-activate-asynchronous</id><content type="html" xml:base="https://antonello.provenza.no/open-source/events/webhooks-activate-asynchronous/"><![CDATA[<p>In today’s interconnected world, effective integration between various systems and services is crucial for the smooth operation of applications: <em><a href="https://en.wikipedia.org/wiki/Webhook">Webhooks</a></em> have emerged as a powerful mechanism for enabling real-time communication and event-driven architectures, enabling consuming applications to be triggered by events occurring in the service provider scope.</p>

<p><img src="/assets/img/2023-11-19-webhooks-activate-asynchronous/Event-Architecture.png" alt="Event Driven Architecture" /></p>

<p>In fact, a typical event-driven architecture uses highly performant messaging systems, generally deployed within the same network of the applications sending and receiving those kind of messages, also to reduce security and performance concerns. However, the typical use case of webhooks is to <em>export</em> such event model outside the boundaries of a system or platform network.</p>

<h2 id="domain-events-and-integration-events">Domain Events and Integration Events</h2>

<p>In the context of a distributed system, events can be classified into two main categories: <em>domain events</em> and <em>integration events</em>.</p>

<p>Domain events are used to model events that occur within the boundaries of a single service or application: they are generally used to notify other components within the same system about changes in the state of the application. For example, a domain event might be triggered when a user places an order in an e-commerce application, or when a new customer is added to the database.</p>

<p>Integration events, on the other hand, are used to model events that occur between different services or applications: they are generally used to notify other systems about changes in the state of the application. For example, an integration event might be triggered when a user places an order in an e-commerce application, or when a new customer is added to the database.</p>

<h2 id="what-are-webhooks">What are Webhooks?</h2>

<p>Webhooks are a lightweight and flexible means of communication used to notify systems about specific <em>integration events</em> or trigger actions: unlike traditional request-response mechanisms, webhooks follow the <em>publish-subscribe</em> pattern, where a sender (known as the <em>webhook provider</em>) sends <em>HTTP POST</em> requests to a specific URL (known as the <em>webhook endpoint</em>) when an event of interest occurs. The receiver (known as the <em>webhook consumer</em>) listens to these events and responds accordingly.</p>

<p>It is the responsibility of the <em>webhook provider</em> to manage the subscriptions to certain events, and ensure that the <em>webhook consumer</em> is notified when an event that matches the subscription occurs that same consumer is notified at the endpoint URL: this might involve several retries, and the <em>webhook provider</em> should be able to handle errors and failures gracefully.</p>

<h2 id="how-to-use-webhooks-to-activate-asynchronous-designs">How to Use Webhooks to Activate Asynchronous Designs</h2>

<p><img src="/assets/img/2023-11-19-webhooks-activate-asynchronous/Webhooks-App-Model.png" alt="Webhooks App Model" /></p>

<p>Webhooks are an excellent tool for enabling asynchronous designs within an integrated architecture: by using webhooks, services can be decoupled, allowing them to communicate with each other without the need for synchronous <em>request-response</em> interactions.</p>

<p>When an event occurs in a platform, the <em>webhook provider</em> can notify all interested consumer applications simultaneously, enabling parallel processing and reducing latency.</p>

<p>A typical webhook payload, since its nature of <em>integration event</em>, also provides contextual information about entities and the occurrence, allowing the <em>webhook consumer</em> to process it accordingly: for example, an e-commerce application might send a webhook notification when a customer places an order, including details such as the <em>order ID</em>, <em>customer name</em>, and <em>shipping address</em>. The <em>webhook consumer</em> can then use this information to update its internal state and trigger other actions, such as sending an email confirmation or updating the inventory.</p>

<p>Webhooks allow systems to scale more effectively by handling events asynchronously. Following the example of the e-commerce application, when a customer places an order, the webhook provider can trigger an event that notifies the inventory management system, the payment gateway, and the shipping system simultaneously. Each system can independently process the event without waiting for the others, leading to improved performance and scalability.</p>

<h2 id="deveel-webhooks">Deveel Webhooks</h2>

<p><em><a href="https://webhooks.deveel.org">Deveel Webhooks</a></em> is an open-source <a href="https://dotnet.microsoft.com">.NET</a> framework that simplifies the implementation and handling of webhooks in .NET applications.</p>

<p>The framework is intended for someone who is looking to implement <em>*-as-a-Service</em> (*aaS) solutions, where the service provider needs to expose webhooks to notify consumers about events occurring in the service. It provides a simple and intuitive API for configuring and managing webhooks, allowing you to focus on the business logic of your application rather than dealing with low-level HTTP interactions.</p>

<h3 id="components-of-the-framework">Components of the Framework</h3>

<p>The framework provides services at various levels of abstraction, allowing you to choose the level of control you need for your application:</p>

<ul>
  <li><strong>Webhook Subscriptions</strong>: the framework provides a generic interface for managing subscriptions to webhooks, allowing you to store them in a database or any other storage medium of your choice. It also provides a default implementation of the interface that uses a MongoDB database for storing subscriptions.</li>
  <li><strong>Webhook Notifications</strong>: it also provides a generic interface for sending notifications to consumers that have subscribed to certain events, allowing you to implement your own webhook provider. It also provides a default implementation of the interface that uses the <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests">ASP.NET Core HTTP Client</a> for sending notifications.</li>
  <li><strong>Webhook Sender</strong>: assuming you are willing to implement your own webhook subscription and notification services, or extending the default contracts, the framework provides a generic interface for sending notifications to consumers that have subscribed to certain events, allowing you to implement your own webhook provider.</li>
</ul>

<h3 id="using-deveel-webhooks-in-your-applications">Using Deveel Webhooks in Your Applications</h3>

<p>You can read more about how to use the framework in your applications in the <a href="https://webhooks.deveel.org/getting-started">documentation</a>, but the following example shows how to configure a webhook provider in your ASP.NET Core application, to start accepting subscriptions and notify consumers about events:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Startup</span> <span class="p">{</span>
  <span class="k">public</span> <span class="k">void</span> <span class="nf">ConfigureServices</span><span class="p">(</span><span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">services</span><span class="p">.</span><span class="n">AddWebhookSubscriptions</span><span class="p">&lt;</span><span class="n">MongoWebhookSubscription</span><span class="p">&gt;(</span><span class="n">options</span> <span class="p">=&gt;</span> <span class="p">{</span>
      <span class="n">options</span><span class="p">.</span><span class="nf">UseConnectionString</span><span class="p">(</span><span class="s">"mongodb://localhost:27017"</span><span class="p">);</span>
    <span class="p">});</span>

    <span class="n">services</span><span class="p">.</span><span class="n">AddWebhooksNotifier</span><span class="p">&lt;</span><span class="n">MyWebhook</span><span class="p">&gt;();</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The above example shows how to configure the framework to use a MongoDB database for storing subscriptions, and a custom webhook implementation for sending notifications to consumers: the framework provides a default implementation of the <code class="language-plaintext highlighter-rouge">IWebhook</code> interface, but you can also implement your own if you need to customize the behavior of the webhook provider.</p>

<p>This assumes that you are using the same instance of an ASP.NET Core application to implement both the webhook subscription management and the notification service: if you are using separate instances, you can use the <code class="language-plaintext highlighter-rouge">AddWebhookSubscriptions</code> method to configure the webhook provider in one application, and the <code class="language-plaintext highlighter-rouge">AddWebhooksNotifier</code> method to configure the webhook notifier in an second one, to obtain a better scalability of resources (suggested option).</p>

<h3 id="event-transformations">Event Transformations</h3>

<p>In some scenarios you might need to transform the event payload before sending it to the consumer: for example, you might need to add additional information to the payload, or change the format of the data.</p>

<p>The framework provides a generic interface for implementing event transformations, allowing you to implement your own transformation logic, based on the type of event you are sending to the consumer.</p>

<p><strong>Note</strong>: please consider that the above example refers to the current version of the framework (<em>v2.1.5</em>), and it might be subject to changes in the future: it is always recommended to refer to the <a href="https://webhooks.deveel.org/">documentation</a> for the latest information, since at the time you are reading this post, the framework might have been updated.</p>

<p>Refer to the <a href="https://github.com/deveel/deveel.webhooks">GitHub repository of the framework</a> for more information about the project, and how to contribute to it.</p>

<h2 id="webhooks-and-cloudevents">Webhooks and CloudEvents</h2>

<p><a href="https://cloudevents.io/">CloudEvents</a> is a specification for describing event data in a common format, making it easier to integrate systems that use different event formats. It provides a standardized way to express event metadata and payload, ensuring interoperability across various platforms and programming languages.</p>

<p>The main purpose and scope of use of CloudEvents is anyway still limited to the boundaries of a single network, and it doesn’t provide any specification for the transport of events outside the boundaries of a system or platform network.</p>

<h3 id="deveel-webhooks-and-cloudevents">Deveel Webhooks and CloudEvents</h3>

<p>The <a href="https://webhooks.deveel.org">Deveel Webhooks</a> framework doesn’t provide any specific implementation of the CloudEvents specification, since it is intended to be used as a general-purpose framework for implementing webhooks in .NET applications: however, an outstanding feature request is to provide a CloudEvents implementation for the framework, to allow developers to use the framework in conjunction with other CloudEvents-compliant systems.</p>]]></content><author><name>Antonello Provenzano</name></author><category term="open-source" /><category term="events" /><category term="opensource" /><category term="integration" /><category term="webhooks" /><category term="asynchronous" /><category term="deveel" /><summary type="html"><![CDATA[Activate the asynchronous integration patterns with webhooks is a common practice for service providers who wish to provide triggers and updates to their consumers.]]></summary></entry><entry xml:lang="en"><title type="html">The Startup Strategy Risk</title><link href="https://antonello.provenza.no/startup/the-startup-strategy-risk/" rel="alternate" type="text/html" title="The Startup Strategy Risk" /><published>2023-09-12T00:00:00+00:00</published><updated>2023-09-12T00:00:00+00:00</updated><id>https://antonello.provenza.no/startup/the-startup-strategy-risk</id><content type="html" xml:base="https://antonello.provenza.no/startup/the-startup-strategy-risk/"><![CDATA[<p>Starting your own business is a fascinating notion, especially after years of working for giant corporations, with all the restraints and limitations that entails: the idea of being your own boss, of being able to determine what to do and how to do it, is quite enticing. This <em>happy path</em> in a founder’s life eventually leads to the necessity for the business to establish its strategy, and this is when the first disagreements may arise between the founders and other stakeholders (investors, advisors, and so on).</p>

<p>It is a <em>luxury problem</em> of small organizations that are successful and need to be supported in their natural growth, because no organization is self-sustaining: external parties, such as investors or suppliers, will begin to require a clear strategy, a clear vision, and a clear direction, forcing a <em>joyful</em> and <em>immature</em> organization to mature and become a <em>serious</em> one.</p>

<h2 id="the-immaturity-of-management">The Immaturity of Management</h2>

<p><img src="/assets/img/2023-09-12-the-startup-strategy-risk/young-businesspeople.jpg" alt="Young Management" class="align-center" /></p>

<p>Management immaturity is a well-known problem in the startup industry, and it is a natural result of the founders being typically relatively young, with little or no experience managing a firm and with little or no experience managing people. This is a challenge that is typically remedied by recruiting experienced managers to assist the founders in maturing and managing the company, but this is not always the case, and the founders are occasionally left alone on this trip.</p>

<p>One issue that develops in this situation is that the founders are usually very passionate about their idea, and they are very focused on the product, the technology, and the solution that they are producing, rather than the financial side of the organization. This is a very typical problem, and it is usually managed by hiring a CEO to handle the company’s business while the founders focus on the product and technology.</p>

<p>Another situation that is completely unique to the previous one is when the founders are extremely passionate about the business side of the company but are uninterested in the product, technology, or solution that they are developing. This is a very common issue, and it is usually solved by hiring a CTO to oversee the product and technology while the founders concentrate on the business side of the company.</p>

<p>In any case, the pathological condition resulting from both situations is the general diversion of the company and its management from the original vision and strategy, causing general confusion and a general lack of direction, which is a dangerous situation for a startup because it can lead to a general lack of focus, and eventually to the company’s failure.</p>

<h2 id="growth-pain">Growth Pain</h2>

<p><img src="/assets/img/2023-09-12-the-startup-strategy-risk/startup-project-analyze-graph-plans.jpg" alt="Startup Graphs" class="align-center" /></p>

<p>In recent years, I’ve been involved in a couple of instances that exhibited the aforementioned patterns, and I’ve witnessed the implications of a lack of a clear strategy and vision for the company, or even a diverging view among the rest of the management.</p>

<p>Both organizations desired to establish more stable and planned growth in order to attract more investors and lay a more solid foundation for the company’s future, but they didn’t really know what this meant or how to achieve it: they were both in a situation of <em>growth pain</em>, where the company was growing but the management was unable to manage this growth, and the company was in a state of general confusion and lack of direction.</p>

<h3 id="sudden-change-in-management-and-strategy">Sudden Change in Management and Strategy</h3>

<p>In one of these experiences, I was hired to assist the company in establishing a more solid foundation for the Enterprise Architecture, its processes, functions, systems, and data, which also meant establishing a more structured and governed environment: something that contradicted the original vision of one of the founders, who had a poor understanding of the discipline, a generally negative attitude toward it, and a general lack of trust in the people involved in the initiative.</p>

<p>The rest of the founding members were skeptical of the practice, despite not being opposed to it, having worked in large organizations, albeit in roles that <em>suffered</em> the development and governance processes by nature: sitting on the riverbank, they were waiting to see some concrete results before committing to the initiative.</p>

<p>In this context and culture, I could see all of the negative and pathological manifestations of a <em>Agile Manifesto</em> utopia, with several teams working on concurrent matters, using different tools and processes, with little or no coordination between them, and with a general lack of direction and focus: despite the IT department’s moderate size (less than 100 people), and with no cases of acquisitions, the proliferation of systems, libraries, and solutions was immeasurable.</p>

<p>External factors influencing the company’s product performance prompted the board of directors to replace the CEO and several other members of the management team, some of whom were sponsoring the growth strategy and the consolidation of Enterprise Architecture: the new strategy was to <em>shrink</em> the organization’s investments and path to maturation, removing several of the <em>System of Records</em> and <em>System of Engagement</em> that were in place.</p>

<p>Eventually, the majority of the IT staff left, and the firm was compelled to rebrand itself in order to have a fresh start with the remaining personnel (albeit none of my former colleagues are now employed there).</p>

<h3 id="knowing-what-you-want--need">Knowing What you Want / Need</h3>

<p>Another example of such ambiguous strategy was presented to me when I was scouted by a promising startup, recommended by a good friend who turned down the offer first, to be their new CTO, to replace the one who abruptly decided to quit the position: they were a very young company, with management coming from mixed experiences and backgrounds, and facing the situation of investors requiring them to shape up their IT strategy, cost analysis, vision, and direction.</p>

<p>In fact, the qualifications for the new post they were searching for were mixed, because a lot of emphasis in their prospects was placed on <em>strategic vision</em>, <em>analysis</em>, and <em>IT strategy</em> (with a strong emphasis on knowledge of <em>cloud technologies</em>).</p>

<p>After a good interview with the HR (and co-founder), where we discussed the reason, vision, and personal experiences, I was invited to a second interview with the COO, where we discussed the technical aspects of the role, as well as the company’s expectations: the COO was very clear that the company was looking for a <em>hands-on</em> person, who could help them in the <em>transformation</em> of the company, and in the <em>consolidation</em> of the IT department, with a strong emphasis on <em>cloud technologies</em>.</p>

<p>As a last step of the process, I was scheduled an interview with the CEO of the startup: a surprisingly short one (less than 30 minutes), where the person clearly stated <em>not being a technical expert</em>, and that he didn’t know what the company needed in respect of the IT, but he wanted that <em>the mobile application to be faster</em>: learning of my limited front-end skills, the interview was more of less over, and we agreed I was not the person he was looking for, although I stated my recommendation to reconsider the requirements of the role, and to focus on the <em>strategic vision</em> and <em>IT strategy</em> aspects, rather than on the barely research of a <em>front-end developer</em> as a CTO.</p>

<p>In fact the above experience proves some of the issues in the management of a startup:</p>

<ul>
  <li><em>Lack of communication between the founders on the vision for the company</em> - while for two of them long-term vision was the focus, the third one was looking for a quick fix to an immediate issue.</li>
  <li><em>Lack of humility in the management</em>, accepting there are areas of competence they might not be experts in - for example, in this case the likely causes for the slowness of the mobile application were issues in the back-end, rather than in the front-end: network misconfigurations in the load-balancer/gateway, database design issues, location of the infrastructure, etc.</li>
  <li><em>Poor knowledge of the real functions of roles and responsibilities in the IT department</em> and of the real needs of the company_ - the company was not really looking for a <em>strategist</em>, but in reality they were looking for a <em>front-end developer</em> (glorifying the position as CTO) to fix an immediate issue</li>
</ul>

<h4 id="follow-up-of-a-failed-process">Follow-Up of a Failed Process</h4>

<p>The week after the above mentioned final interview, it was announced by the company the person who was hired for the role: a young <em>full-stack developer</em> with little experience in <em>cloud technologies</em>, and with no experience in <em>strategic vision</em> and <em>IT strategy</em>, and 5 years in the industry: a profile fitting the immediate needs of the company, but not the long-term ones.</p>

<p>In fact after approximately four months from that announcement I was reached again from the HR (and co-founder), who initially interviewed me, to ask me if I was still interested in the role, since the person hired was not able to deliver the expected results, and they were looking for a replacement.</p>

<p>You can imagine my answer…</p>

<h2 id="heroes-rockstars-and-ninjas">Heroes, Rockstars and Ninjas</h2>

<p>The mythological figures of the <em>heroes</em> who jumpstart companies, despite the lack of security, and animated by the entrepreneurial spirit of the <em>Western Explorers</em> is fantastic, appealing, fascinating, but it is also a dangerous myth, since it is not realistic, and it is not sustainable in the long term.</p>

<p>When the initial spirit of adventure is turning into the need of a consistent and sustainable growth, the company needs to change its approach, and to start to think in a more structured and planned way, and to establish a more solid foundation for the future: the fun part of <em>jumping without a net</em> is over, and the company needs to start to think about the future, and to plan for it.</p>

<p>Sometimes I make the comparison between starting up an organization with starting a rock band: it is fun at the beginning, and mostly executed in the spare time, with other regular jobs sustaining the living of the members, but when the band starts to get some traction, and the members start to think about a career in music, they need to start to think about the future, and to plan for it. Only few bands ultimately succeed, cause the members, even not liking each other’s, are able to put aside their differences, and to focus on the common goal: the success of the band.</p>

<p>Some <em>rockstars</em> might even have success through a <em>destructive path</em>, with a sudden and unplanned success, but the majority of them will fail, and will be forgotten, and the same is true for the <em>heroes</em> of the startup world.</p>]]></content><author><name>Antonello Provenzano</name></author><category term="startup" /><category term="startup" /><category term="strategy" /><category term="risk" /><summary type="html"><![CDATA[Starting up an enterprise can be messy, and some small conflicts might emerge between the founders. The startup strategy and vision is one of them.]]></summary></entry><entry xml:lang="en"><title type="html">Resume Based Development</title><link href="https://antonello.provenza.no/architecture/development/resume-based-development/" rel="alternate" type="text/html" title="Resume Based Development" /><published>2023-09-03T06:25:34+00:00</published><updated>2023-09-03T06:25:34+00:00</updated><id>https://antonello.provenza.no/architecture/development/resume-based-development</id><content type="html" xml:base="https://antonello.provenza.no/architecture/development/resume-based-development/"><![CDATA[<p>Projects and services are sometimes developed by architects and developers with the sole intention of enhancing their resumes, rather than addressing real problems. This practice poses significant issues as it results in wastage, frustration, technical debt, and the creation of unmaintainable code.</p>

<!--more-->

<h2 id="the-dynamics-of-resume-based-development">The Dynamics of Resume Based Development</h2>

<p><img src="/assets/img/2023-09-03-resume-based-development/resume-apply-work-form.jpg" alt="Resume" class="align-center" /></p>

<p>Organizations with an established agile software development process are more susceptible to encountering a <em>resume-based development</em> approach. This vulnerability arises due to the significant autonomy afforded to agile teams in determining both what to build and how to build it.</p>

<p>One of the fundamental skills to seek in a developer is the ability to learn new technologies and frameworks. Consequently, developers are frequently provided with opportunities to acquire these skills. Regrettably, this sometimes results in the adoption of trendy and <em>hyped</em> technologies and frameworks that do not serve the immediate problem.</p>

<p>Occasionally, this choice is motivated by a genuine desire to equip the organization with a modern, future-proof solution. However, in many instances (as per my experience, the majority), it represents a <em>self-serving</em> decision aimed at enhancing the developer’s resume for internal career growth or external job prospects.</p>

<h3 id="overengineering-solutions-to-problems">Overengineering Solutions to Problems</h3>

<p>Assuming a well defined structure of an agile team, where the product owner has a clear vision of the product and good grip on the marker, the implementation of the service(s) that provide the functions of the product is left to the team, in which one of the developers is generally the <em>architect</em>, meaning the one who ultimately makes the architectural, technical, decisions.</p>

<p>In this phase of inception, the acting architect might use the opportunity given by the <em>green field</em> solution to experiment with new technologies and frameworks, which is not necessarily a bad thing, but it can lead to the adoption of a solution that is not the best fit for the problem at hand.</p>

<p>I had to face myself several occasions where the implementation of a service was overengineered to experiment with a new technology, implementing quite simple requirements with a very complicated and unknown approach, or even worse not achieving the goal at all.</p>

<h2 id="the-impact-on-the-organization">The Impact on the Organization</h2>

<p><img src="/assets/img/2023-09-03-resume-based-development/group-business-colleagues-having-headache.jpg" alt="Colleagues Having Headache" /> {: .align-center }</p>

<p>Working with mature technologies and frameworks surely provides benefits like <em>predictability</em>, <em>support</em>, <em>patterns</em>, while at the contrary adopting innovative approaches to the implementation of services introduces a lot of <em>uncertainty</em> and <em>risk</em>.</p>

<p>Product managers, product owners, and business analysts, are often not aware of the technical implications of the choices made by the team, and they are not able to assess the risk of adopting a new technology or framework, which is often not even mentioned in the requirements: here the relationship and trust between parties is key to avoid the adoption of a solution that is not the best fit for the problem at hand.</p>

<p>The uncertainty and risk associated with the adoption of new technologies often lead to a slower development pace and, in the worst-case scenario, project failure - a significant waste of both time and resources for the organization.</p>

<h3 id="the-cqrs-and-event-sourcing-example">The CQRS and Event Sourcing Example</h3>

<p>A few years ago, before <a href="https://martinfowler.com/bliki/CQRS.html">CQRS</a> became a well-known pattern, I encountered a situation in which one of the developers responsible for the <em>architecture</em> of a critical security component, vital for the large-scale solution I was working on, chose to implement it using <em>CQRS and Event Sourcing</em> as a greenfield solution, contrary to my recommendations to rely on mature technologies such as <a href="https://en.wikipedia.org/wiki/Active_Directory">Active Directory</a>.</p>

<p>None at that time, either within the organization or in the industry, knew CQRS and Event Sourcing well, and I had to study the pattern myself, to understand the implications of the choice made by the developer: given the uncertainty and risk introduced by the adoption of a new pattern, and the lack of any vendor to support it (in fact, the developer himself implemented a framework to support it), I asked him and the organization to desist and adopt a more mature technology, but I was overruled.</p>

<p>Two years passed by, through implementations, experiments, failures, without the component to ever be functional, with an enormous waste of time and money for the organization, and with the developer to leave the organization, leaving the component in a state of <em>limbo</em>.</p>

<p>Ultimately, the organization decided to scrap that component from the architecture and to adopt a more mature technology, with a reputational damage for those who supported the expense to support and immature technology, to implement a component from ground up, not representing the core business of the organization, and even failing to deliver it.</p>

<h2 id="the-architecture-bias">The Architecture Bias</h2>

<p>Superficially, some organizations consider the role of an architect just the realization of designs and diagrams, to provide a set of <em>work packages</em> to the developers, and to review the code produced, with a naive approach to what the deliverables of an architecture should be. Some organizations even consider the role of an architect as a <em>senior developer</em>, who is not involved in the complex assessments of <em>IT strategy</em> and <em>IT governance</em>, worried that this might slow down the delivery of the product.</p>

<p>Being an architect is not just about dealing with code patterns, performance and scalability, but it is also, and more prominently, about considering the whole context in which a service or a component is introduced, the IT strategy of the organization, the costs failures and recovery: in many occasions, these responsibilities have been stripped out of the role of an architect, and the role has been reduced to a <em>senior developer</em>. Or even worse, some developers have been granted the title for internal career advancement, without the proper skills and experience.</p>

<p>I am not exonerated from this bias, since I must confess that I failed myself in the past to consider what the architect role meant with this regard, and I have been guilty of the <em>resume based development</em> approach myself, at least in one occasion: we learn, we grow, we improve, we share our experience with the aim of helping others to avoid the same mistakes.</p>

<h2 id="architects-make-it-boring">Architects Make it Boring</h2>

<p>One of the main criticism towards architects is that they make things boring, and this is true: architects are not there to make things exciting, but to make things work, and to make things work in the most efficient way, with the least amount of risk and uncertainty.</p>

<p>The choice of adopting new technologies and patterns to implement a service is not necessarily a bad thing, but it should be done with the right approach, with the right assessment of the risk and uncertainty, and with the right support from the organization: impact of a failed delivery ripples its effects through the whole organization, and it is not just a matter of the reputation of the architect, but of the whole cohort of people involved.</p>

<h3 id="the-innovation-stream">The Innovation Stream</h3>

<p><img src="/assets/img/2023-09-03-resume-based-development/javascript-developer.jpg" alt="Developer at Work" class="align-center" /></p>

<p>Developers are generally (and hopefully) attracted by innovation, experimentation, and learning: being in a job where you can be excited by the opportunity to learn new technologies, free from the burden of risk, is a key motivator for developers to join a company, grow with it and stay.</p>

<p>Regrettably, the adoption of new technologies and patterns does not always proceed smoothly, and success is not guaranteed. While offering a secure environment for innovation within the organization is vital for attracting and retaining talent, it is equally crucial to prevent the adoption of new technologies and patterns from being solely driven by the <em>resume-based development</em> approach.</p>

<p>Mature organizations allocate resources and time to the <em>innovation stream</em>, where product development teams feel safe to experiment with new technologies, evaluating their complexity and risks, learning from the experience, and sharing the knowledge with the rest of the organization.</p>

<p>Implementing such a process is an important investment for an organization, which is not assured to provide a return, and it cannot be taken lightly: in fact, only few large organizations can afford to have such a process in place, and it is not a surprise that they are the ones that are able to attract and retain talents.</p>

<p>Unfortunately, the vast majority of organizations should instead accept to rely on mature technologies and patterns, and to provide their developers with the opportunity to learn new things in their spare time, or by attending conferences and meetups, or by providing them with a budget to attend courses and training: in such contexts product owners and architects act in their role of <em>governance</em> trying to reduce risks from the adoption of innovative technologies that are not proven to be the best fit for the problem at hand.</p>]]></content><author><name>Antonello Provenzano</name></author><category term="architecture" /><category term="development" /><category term="architecture" /><category term="development" /><category term="organization" /><category term="process" /><category term="software" /><category term="devsecops" /><category term="risk" /><category term="patterns" /><summary type="html"><![CDATA[Projects and services are sometimes built by architects and developers to look good on a resume, not to solve a problem.]]></summary></entry><entry xml:lang="en"><title type="html">The Dynamics of Emerging Architecture in Agile Organizations</title><link href="https://antonello.provenza.no/architecture/the-dynamics-of-emerging-architecture/" rel="alternate" type="text/html" title="The Dynamics of Emerging Architecture in Agile Organizations" /><published>2023-08-28T07:23:22+00:00</published><updated>2023-08-28T07:23:22+00:00</updated><id>https://antonello.provenza.no/architecture/the-dynamics-of-emerging-architecture</id><content type="html" xml:base="https://antonello.provenza.no/architecture/the-dynamics-of-emerging-architecture/"><![CDATA[<p>Simple organizations, especially those who are in a <em>startup</em> phase, are usually characterized by a small number of people, who are able to cover multiple roles and functions, and where the communication is direct and informal.</p>

<p>The design of the overall architecture of the systems, applications and services consisting the organization is usually the result of a single vision, which is often the result of the founder’s experience and knowledge, and the result of the first implementations of the organization: these are usually easy to change, adapt, and evolve, since they are owned by a single person or a single team.</p>

<p>In such a scenario, the <em>enterprise architecture</em> is usually not a concern, since the organization is still small and the systems are not yet complex enough to require a dedicated function, the technological stack is usually homogeneous and the communication between the different components is direct and simple.</p>

<h2 id="moving-to-the-next-level">Moving to the Next Level</h2>

<p><img src="/assets/img/2023-08-28-the-dynamics-of-emerging-architecture/colleagues-working-together-planning.jpg" alt="Organization" class="align-center" /></p>

<p>When the organization starts to grow, the number of people involved in the development of the systems increases, and the number of systems and applications increases, the complexity of the overall architecture increases, and the need for a dedicated function to manage it becomes more and more evident.</p>

<p>To such a complexity and <em>growth pain</em>, it is usually added the need to maintain the existing systems, which are usually not designed to be easily changed, since they were typically implemented to serve an immediate need, and not to be part of a long-term strategy: such systems are usually called <em>legacy</em> systems, and they are usually the most difficult to change, since they are the result of a <em><a href="https://en.wikipedia.org/wiki/Legacy_system">legacy architecture</a></em>.</p>

<p>In several situations, I found myself approaching the source code of some of these applications, to trying make sense of the design choices, and I found myself wondering <em>‘what were they thinking?’</em>. In fact, the design of such systems is usually the result of a <em><a href="https://en.wikipedia.org/wiki/Design_by_committee">design by committee</a></em> approach, where the different stakeholders of the organization, with different backgrounds, knowledge and objectives, have contributed to the design of the system, without a clear vision and strategy. In addition to this, it is typical that the most senior technical figures who led the design of such systems had no training in the best practices of software design, and they were usually the most senior developers, who were promoted to the role of <em>technical lead</em> or <em>architect</em>.</p>

<p>Anyway, organizations who grow face new problems and challenges, often underestimated and not considered in the initial design of the systems, and they are forced to adapt and evolve the architecture of their systems, to make them more resilient, scalable, maintainable and secure.</p>

<p>For example, a growth in the number of users of a system might require to <a href="https://en.wikipedia.org/wiki/Scalability#Horizontal_(scale_out)_and_vertical_scaling_(scale_up)">scale the system</a> <em>horizontally</em>, to increase the number of instances of the application, to serve the increased number of requests, or to scale the system <em>vertically</em>, to increase the capacity of the existing instances, to serve the increased number of requests, or to acquire services from external providers, terminating some functions currently implemented in the system.</p>

<h2 id="the-emergence-of-architectures">The Emergence of Architectures</h2>

<p>The evolution of the architecture of the systems of an organization is usually the result of a series of <em>emergent</em> designs, which are the result of the <em>emergent</em> behaviors of the organization, and not of a single architectural vision.</p>

<p>As much as one might want to academically state that the architecture of an enterprise can be clearly defined and designed, the reality is that the architecture of an organization is the result of the <em>emergent</em> behaviors of the organization itself: choices are taken by decentralized teams, with the ambitions of solving the problems they are facing, and not of designing the overall architecture of the organization.</p>

<p>These dynamics within the organization are usually the result of the <em><a href="https://en.wikipedia.org/wiki/Conway%27s_law">Conway’s Law</a></em>, which states that</p>

<blockquote>
  <p>‘Organizations which design systems […] are constrained to produce designs which are copies of the communication structures of these organizations’</p>
</blockquote>

<p>Such a dynamic is particularly observable in <em>Agile</em> organizations, where the teams are self-organized and autonomous, and they are not constrained by a centralized architecture function, producing a multitude of services and applications which implement each one a specific set of patterns and practices, reflecting the skills and knowledge of the team who designed and implemented them, rather than a coherent and consistent architecture.</p>

<p>Enterprise Architects who belong to such organizations should resist the impulse to <em>rationalize</em> and operate any <em>military force</em> to restrain such movements, frustrating the values and mission of the organization, that intentionally has chosen such technical paths, but rather try to make sense of the <em>emergent</em> architecture, trying to identify the patterns and the commonalities between the different systems, and trying to identify the best practices and the standards to be applied to the different systems.</p>

<p>I never found it productive trying to impose a <em>top-down</em> approach to the design of the architecture of an organization, going against the existing asset of people, skills and systems, but rather trying to influence them, by providing the teams with the right tools, standards and best practices, to make them more effective and efficient, communicative and collaborative.</p>

<h2 id="mixing-intentional-and-emergent-architectures">Mixing Intentional and Emergent Architectures</h2>

<p><img src="/assets/img/2023-08-28-the-dynamics-of-emerging-architecture/Comic-agile_213.png" alt="Emerging Architecture Comic" class="align-center" /></p>

<p>As mentioned above, successful organizations grow and evolve, and with them the enterprise architecture of the systems they are composed of: this means that large portions of the functions implemented internally (eg. CRM, ERP, communications, etc.) are usually replaced by external services, externalizing costs, risks, resources, and focusing on the core business of the organization.</p>

<p>In such a scenario, the core function of Enterprise Architects is to design <em>intentional architectures</em>, setting the target for the integrations and the interactions between the different systems, assessing ahead of time the target state of the final architecture, and then working with the different teams to implement the required changes.</p>

<p>How can then a successful organization can mix the <em>intentional architecture</em> with the <em>emergent architecture</em>?</p>

<p>The answer I have given myself (without any ambition of being the right one), is to make <em>intentional architecture</em> as <em>emergent</em>, providing the teams with the right tools, standards and best practices, to make them more effective and efficient, communicative and collaborative, so that the skillset of the teams can be leveraged to design and implement the architecture of the various components forming the target architecture, achieving a good level of <em>buy-in</em> from the various parts of the organization, especially those who are more resistant to change (such as the development teams, who are usually more focused on the short-term objectives, rather than the long-term ones).</p>

<h2 id="collaborative-design">Collaborative Design</h2>

<p><img src="/assets/img/2023-08-28-the-dynamics-of-emerging-architecture/software-developers-sitting-desk.jpg" alt="Collaborative Design" class="align-center" /></p>

<p>Although it is not always possible to control precisely the design of the architecture of an organization, it is anyway possible to influence it, by providing the teams with the right tools, standards and best practices, to make them more effective and efficient, communicative and collaborative: ultimately, the sharing of the common knowledge, patterns, practices and standards, will make the teams more effective and efficient, and will make the architecture of the organization more consistent and coherent.</p>

<p>For an organization, keeping the set of principles, standards, technologies (aka the <em>“IT Strategy”</em>) consistent is a matter of cost control, investment opportunities, resource management, and ultimately of <em><a href="https://en.wikipedia.org/wiki/Technical_debt">technical debt</a></em>: the more the organization is able to keep the <em>IT Strategy</em> consistent, the more it will be able to control the costs of the systems, the investments in new technologies, the management of the resources, and the reduction of the technical debt.</p>

<h2 id="managing-the-expectations">Managing the Expectations</h2>

<p>The core aspect to manage when applying such an <em>agile</em> approach to enterprise architecture is the expectations of the stakeholders of the organization, who are usually looking towards a clear and defined architecture, with a clear and defined roadmap, and not an <em>emergent</em> architecture, which delegates responsibilities of the design to the teams, low in the organization chart and level of details, introducing insecurity and uncertainty.</p>

<p>One of the key aspects considered by mature management of an organization, when hiring an Enterprise Architect, is to remove the unpredictability of target states of the IT landscape, and to reduce or eliminate the uncontrolled fluctuation of costs, things that ideally require a full control of the architect over the whole set of technologies, development processes, infrastructures and possibly people: something that is in contrast with the <em>agile</em> approach to enterprise architecture, where the architect is not the one who designs the architecture, but rather the one who coordinates the design process, and the one who provides the teams with the right tools, standards and best practices, to make them more effective and efficient, communicative and collaborative.</p>

<p>I have also generally observed (and it’s part of the academic literature on the matter) that teams, rightfully, tend to refuse the responsibility of design an overall architecture that is not under their immediate control, and that they are not able to influence: this is a natural reaction to the fear of being blamed for the failure of the architecture, and the fear of being held responsible for the consequences of the design choices, especially when the teams are not involved in the decision making process.</p>

<h3 id="the-role-of-the-enterprise-architect">The Role of the Enterprise Architect</h3>

<p><img src="/assets/img/2023-08-28-the-dynamics-of-emerging-architecture/man-working-night_1098-12798.png" alt="Architect Working" class="align-center" /></p>

<p>Without frustrating the agile ambitions of the organization, the management should then expect an Enterprise Architect to work as a facilitator of the emerging designs of the single parts of the overall enterprise architecture, without any ambition that he/she would be able to control the various parts in details.</p>

<p>The concept of “architecture” was imported into software development from the construction industry, where the architect is the one who designs the building, and then the construction workers are the ones who build it, following the design of the architect: this parallelism had (and in some, very niche, contexts still <em>has</em>) a meaning following the <em>waterfall</em> approach to software development, where the design of the architecture is done upfront, and then the development teams are the ones who implement it, following the design of the architect.</p>

<p>In today’s <em>Agile</em> world of IT, the design of the architecture is not done upfront, but rather it is done <em>emergently</em>, and thus the role of the Enterprise Architect has changed as well, becoming a coordinator of the design process, rather than the designer: that is something to be communicated and understood by those organizations who strive in situations where <em>agility</em> is perceived as a necessity to attract talents, but on the other hand they are suffer the consequences of such choice (eg. lack of control, lack of visibility, lack of predictability, etc.).</p>]]></content><author><name>Antonello Provenzano</name></author><category term="architecture" /><category term="architecture" /><category term="organization" /><category term="agile" /><category term="design" /><category term="organization" /><summary type="html"><![CDATA[Organizations are complex and layered systems, where applications and services are the result of designs over time and not of a single architectural vision.]]></summary></entry><entry xml:lang="en"><title type="html">The Microservices Dilemma</title><link href="https://antonello.provenza.no/architecture/the-microservices-dilemma/" rel="alternate" type="text/html" title="The Microservices Dilemma" /><published>2023-08-06T12:21:00+00:00</published><updated>2023-08-06T12:21:00+00:00</updated><id>https://antonello.provenza.no/architecture/the-microservices-dilemma</id><content type="html" xml:base="https://antonello.provenza.no/architecture/the-microservices-dilemma/"><![CDATA[<p>Since few years now the hype of the microservices architecture is growing and growing. It is a popular architecture for building distributed applications, separating and isolating the technologies needed to implement <em>domain-driven</em> services, such as they can be developed, deployed and scaled independently by one or more teams. But how they differ from a traditional SOA?</p>

<h2 id="the-service-oriented-architecture-soa-pattern">The Service Oriented Architecture (SOA) Pattern</h2>

<p><img src="/assets/img/2023-08-06-the-microservices-dilemma/soa-ref-architecture.jpg" alt="The SOA" /></p>

<p>The <em>Service Oriented Architecture</em> (SOA) is an architectural style that defines the use of services to support the requirements of software users. A service is a self-contained unit of functionality, such as retrieving an online bank statement: by that definition, a service is a discrete unit of functionality that can be accessed remotely and acted upon and updated independently, such as retrieving a credit card statement online.</p>

<p>Such a model has been used for several years in the enterprise world, but it has been often misused and misunderstood. The SOA is not a technology, but an architectural style, and it is not a silver bullet for all the problems of the enterprise world. It is a way to design and implement software systems that are loosely coupled, interoperable, and reusable.</p>

<p>According to the pattern, a system, or even an entire enterprise, can be composed by integrating several external services, which provide each a portion of the information and the functions needed to implement a business process. The services can be implemented using different technologies and can be deployed on different platforms, but they must be able to communicate with each other using a common communication protocol.</p>

<p>Nothing unknown to almost all of you who are reading until now.</p>

<h2 id="the-microservices-pattern">The Microservices Pattern</h2>

<p><img src="/assets/img/2023-08-06-the-microservices-dilemma/ms-microservices-arch-example.png" alt="The Microservices" />
<sub>Copyright © 2018 Microsoft - <a href="https://devblogs.microsoft.com/cesardelatorre/designing-and-implementing-api-gateways-with-ocelot-in-a-microservices-and-container-based-architecture/">Designing and implementing API Gateways with Ocelot in .NET Core containers and microservices architectures</a></sub></p>

<p>The terminology “<em>Microservice</em>” first appeared in 2011, in <a href="https://martinfowler.com/articles/microservices.html">a blog post</a> by <a href="https://www.thoughtworks.com/profiles/j/james-lewis">James Lewis</a> and <a href="https://martinfowler.com/">Martin Fowler</a>, and it is a variant of the SOA architectural style. Summarizing the principles of the paper, the microservices architecture is a style of software architecture that has the following characteristics:</p>

<ul>
  <li>The application is composed by several small services, each one running in its own process and communicating with lightweight mechanisms, often an HTTP resource API.</li>
  <li>Each service is built around business capabilities.</li>
  <li>Services are independently deployable.</li>
  <li>Services are independently scalable.</li>
  <li>Services can be implemented using different technologies.</li>
  <li>Services are owned by small teams.</li>
</ul>

<p>As such, the overall ambition of the pattern is not to dictate any specific technology or solution for the implementation, but instead to provide a set of guidelines that can be used to design and implement a distributed application, focusing mostly on the concept of <em>products</em>, rather than <em>projects</em>, and enabling a greater agility in the development and deployment of the <em>features</em>.</p>

<h3 id="the-evolution-of-the-microservices">The Evolution of the Microservices</h3>

<p>Like many good ideas, the microservices architecture has been misused and misunderstood, and it has been often used as a synonym of <em>distributed architecture</em>, or <em>distributed system</em>, or <em>service-oriented architecture</em>. But the microservices architecture is not a synonym of any of these terms, and it is not a silver bullet for all the problems of the enterprise world.</p>

<p>In fact its adoption was helped by the emergence of technologies developed for the support of cloud-native applications, such as containers (eg. <a href="https://docker.com">Docker</a>), container orchestrators (eg. [Kubernetes]<a href="https://kubernetes.io">https://kubernetes.io</a>), <a href="https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-overview">Service Fabric</a>), and cloud platforms (eg. <a href="https://azure.net">Azure</a>, <a href="https://cloud.google.com">Google Cloud</a>), which have been used to implement distributed applications, although they are not a requirement for the adoption of the pattern.
Despite not being required, the majority of the IT practitioners implemented the microservices pattern by adopting a specific technological stack, heavily based on the <em>Docker</em> and <em>Kubernetes</em> components, with all its complexities and challenges, often without understanding the real orientation of the pattern.</p>

<p>Naturally, several of the critical aspects of a microservice architecture, such as the development and operational processes, the communication protocols, the data management, the security, and the monitoring, are not new, and they have been already addressed by the <em>SOA</em> architectural style, but the radical approach to define even smaller portions of a service domain, to be served independently, multiplied the complexity of the infrastructure needed to implement the pattern (service-specific databases, separated monitoring and alerting tools, multiple autonomous development and deployment processes), also to address the organizational change emerging from the philosophy of the pattern.</p>

<h3 id="containers-orchestration-and-platforms">Containers, Orchestration, and Platforms</h3>

<p>The adoption of containers, container orchestrators, and cloud platforms, has been a natural evolution of the microservices architecture, but it has been often misunderstood as a requirement for the adoption of the pattern. In fact, the microservices architecture can be implemented using any technology, and it can be deployed on any platform, but the adoption of these technologies can scale up the complexity for the development and deployment of the services.</p>

<p>Since the microservice is indeed a pattern, and not a specific technology, the architecture of a system might be respecting these principles through the adoption of provider-specific technologies (eg. <em>Azure App Services</em>, <em>Amazon EC2 Instances</em>, etc.), or even through the adoption of a monolithic architecture, where the services are implemented as libraries, and they are deployed as part of a single application: it is responsibility of an architect to define where the physical boundaries of a service are, and how to implement them.</p>

<p>Anyway, the usage of <em>orchestration platforms</em> to implement this pattern has been popularized by their alleged scalability and resilience, enhanced by the myth of <em>cloud portability</em> (the concept, never really achieved, of realizing an entire system resilient to the provider of cloud infrastructure, or even the on-premise provisioning), which instead turned up in practice to require more efforts to configure and maintain the infrastructure, to implement the services, and ultimately consisted of a parallel set of technologies to be learned and mastered.</p>

<h3 id="missed-business-opportunities">Missed Business Opportunities</h3>

<p>Some of the major arguments and drivers, from the business side of the IT, for the adoption of the microservices architecture, have been:</p>

<ul>
  <li><strong>Agility</strong>: the ability to develop and deploy new features faster, and to scale the services independently, without the need to coordinate the development and deployment of the entire system.</li>
  <li><strong>Resilience</strong>: the ability to isolate the failures of a service, and to recover from them, without affecting the entire system.</li>
  <li><strong>Scalability</strong>: the ability to scale the services independently, and to optimize the resources needed to run the system.</li>
  <li><strong>Technology Independence</strong>: the ability to implement the services using different technologies, and to adopt the best technology for each service.</li>
  <li><strong>Team Independence</strong>: the ability to assign a team to a service, and to let the team to develop and deploy the service independently.</li>
  <li><strong>Business Independence</strong>: the ability to assign a business domain to a service, and to let the business to manage the service independently.</li>
  <li><strong>Cost Reduction</strong>: the ability to reduce the costs of the infrastructure, and to optimize the resources needed to run the system.</li>
  <li><strong>Cloud Portability</strong>: the ability to deploy the system on any cloud platform, and to migrate the system from one cloud platform to another.</li>
</ul>

<p>As debated already in the previous paragraphs, some of these arguments have been proven by experience to be not so true, or at least not so easy to achieve, and the adoption of the pattern has been often driven by the hype of the technology, rather than by the real business needs.</p>

<p>In fact the hidden costs of setting up an organization that can handle a microservice architecture are often underestimated, and impact largely by surprise the business side of the IT, resulting in re-evaluation of plans, roadmaps, and budgets, and in the worst cases in the abandonment of the pattern, and the return to a monolithic architecture.</p>

<p>As much as from an IT Strategy standpoint the adoption of microservices can be a good choice, aimed to diversify the technology stack, opening to more opportunities in the market (people, technology vendors, cloud providers), the reality of the enterprise processes is to rely on a single set of technologies, skillset and vendors, to better forecast the costs and the risks of the business: such dynamics counters the arguments of <em>Technology Independence</em> and <em>Cloud Portability</em>, and it is often the reason for the failure of the adoption of the pattern.</p>

<h2 id="is-it-worth-it">Is It Worth It?</h2>

<p>A couple of months ago (at the time I am writing this post), a team at Amazon made a lot of sensation in the industry, after the publication of an article titled <a href="https://aws.amazon.com/blogs/architecture/why-amazon-chose-a-monolithic-architecture-for-prime-video/">Why Amazon Chose a Monolithic Architecture for Prime Video</a> (and the related <a href="https://www.youtube.com/watch?v=ZgdS0EUmn70">video</a>), supporting the monolithic architecture of their new media service, that has been often used as a reference to argue the failure of the microservices architecture, and the superiority of the monolithic architecture.</p>

<p>Although this article has been a sort of <em>relief</em> for many architects and IT practitioners, frustrated by the development and operational challenges provided by the microservices architecture, including myself, I personally think that is too early to declare the failure of the pattern, and to return to the monolithic architecture for any kind of system.</p>

<p>Anyway, the pattern itself might still provide of high business value, independently from its technological implementation, especially in contexts of mixed technological stacks, such as in the cases of Merge &amp; Acquisition, or in the cases of legacy systems, where the adoption of the pattern can be a good choice to modernize the system, and to open to new opportunities in the market.</p>

<p>One of the untold dynamics in the enterprise is indeed the primacy of the technology over the business, leading to weak positions of the business side of the IT, in favor of the development functions, who are more prone to experiment with new technologies, rather than to adopt the best technology for the business: to counter this dynamic, the business functions have over time introduced more and more agile processes aimed to involve developers into the decision making, and to align the technology with the business needs, but the reality is that the business is still often forced to adopt the technology chosen by the development functions, rather than the other way around.</p>

<h2 id="what-is-a-microservice">What is a Microservice?</h2>

<p>At the end of the day, I believe that the major misunderstanding of the microservices architecture is the definition of <em>microservice</em> itself, which is often confused with a <em>service</em>, or a <em>component</em>, or a <em>library</em>, or a <em>module</em>: the nature of the <em>bounded context</em> of the service is an unavoidable aspect of the pattern, and it is often overlooked, or misunderstood, leading to the implementation of a distributed monolith, rather than a distributed system.</p>

<p>In fact, several of the information and functions of a service are often shared with other services, and the boundaries of the service are often defined by the technology, rather than by the business domain, often leading to the re-implementation of the same existing functions, motivated by technological choices (communication efficiency within the same technology stack, modernization of the implementation), rather than by business needs.</p>

<p>It than becomes crucial to the successful implementation of the pattern to define the boundaries of the services, and to identify the business domains, and the business functions, that are the real drivers of the architecture, and to implement the services accordingly, rather than to follow the hype of the technology.</p>]]></content><author><name>Antonello Provenzano</name></author><category term="architecture" /><category term="microservices" /><category term="architecture" /><category term="integration" /><category term="design" /><category term="patterns" /><summary type="html"><![CDATA[Microservices is a popular architecture for building distributed applications, but how they differ from a traditional SOA?]]></summary></entry></feed>