<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dariusz Gafka</title>
    <description>The latest articles on DEV Community by Dariusz Gafka (@dgafka).</description>
    <link>https://dev.to/dgafka</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3679749%2F21b8189c-9297-4ece-ba6b-ad1591cf221a.jpeg</url>
      <title>DEV Community: Dariusz Gafka</title>
      <link>https://dev.to/dgafka</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dgafka"/>
    <language>en</language>
    <item>
      <title>Your Legacy PHP Codebase Isn't Hopeless</title>
      <dc:creator>Dariusz Gafka</dc:creator>
      <pubDate>Mon, 26 Jan 2026 22:05:16 +0000</pubDate>
      <link>https://dev.to/dgafka/your-legacy-php-codebase-isnt-hopeless-16l0</link>
      <guid>https://dev.to/dgafka/your-legacy-php-codebase-isnt-hopeless-16l0</guid>
      <description>&lt;p&gt;You ship a small bug fix. Suddenly, two other features break. Every deployment feels like gambling. The business depends on this app — it brings in revenue, customers use it daily — but nobody feels confident working on it.&lt;/p&gt;

&lt;p&gt;You open a file that should be a simple list of functions and find a 2,000‑line monolith of nested loops and if‑statements. Comments like &lt;code&gt;// Temporary fix&lt;/code&gt; from years before. Presentation, database queries, and business logic all mixed together in what one developer described as a “glorious spaghetti mashup.”&lt;/p&gt;

&lt;p&gt;You're not alone. And your codebase isn't hopeless.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Industry’s Dirty Secret
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;~13% of Composer installs still run end‑of‑life PHP versions
&lt;/li&gt;
&lt;li&gt;~27% are on EOL or security‑only versions
&lt;/li&gt;
&lt;li&gt;Over 50% of popular PHP packages support unsupported PHP versions
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Legacy systems are the norm, not the exception.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Trap: Rewrite or Suffer
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rewrites have ~23% success rate
&lt;/li&gt;
&lt;li&gt;Incremental modernization has ~53% success rate
&lt;/li&gt;
&lt;li&gt;Small changes fail only ~4% of the time
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rewrites discard years of encoded business knowledge.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Third Path: Incremental Transformation
&lt;/h2&gt;

&lt;p&gt;Instead of rewriting everything:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify a painful area
&lt;/li&gt;
&lt;li&gt;Extract logic into a message handler
&lt;/li&gt;
&lt;li&gt;Test it in isolation
&lt;/li&gt;
&lt;li&gt;Make it async if needed
&lt;/li&gt;
&lt;li&gt;Repeat
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is exactly what &lt;strong&gt;Ecotone&lt;/strong&gt; enables.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example: The 800‑Line Controller
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;placeOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// validation, persistence, payments, emails, analytics...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 1: Extract an Event
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderWasPlaced&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nv"&gt;$total&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderNotificationHandler&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[EventHandler]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;sendConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;OrderWasPlaced&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Publish From Legacy Code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;eventBus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrderWasPlaced&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3: Test in Isolation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderNotificationHandlerTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;TestCase&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Make It Async
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="na"&gt;#[Asynchronous('notifications')]&lt;/span&gt;
&lt;span class="na"&gt;#[EventHandler]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;sendConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;OrderWasPlaced&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Repeat
&lt;/h2&gt;

&lt;p&gt;Analytics, loyalty points, reporting — each becomes isolated and testable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Command Handlers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlaceOrder&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderHandler&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;commandBus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PlaceOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Database Queries
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderQueries&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[DbalQuery(...)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getPendingOrders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Resilience &amp;amp; Retries
&lt;/h2&gt;

&lt;p&gt;Retries, DLQs, and error handling are declarative and consistent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Idempotency
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="na"&gt;#[Deduplicated('orderId')]&lt;/span&gt;
&lt;span class="na"&gt;#[CommandHandler]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;placeOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;PlaceOrder&lt;/span&gt; &lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;System keeps running
&lt;/li&gt;
&lt;li&gt;Small PRs
&lt;/li&gt;
&lt;li&gt;Fast tests
&lt;/li&gt;
&lt;li&gt;Compounding improvements
&lt;/li&gt;
&lt;li&gt;Attracts senior engineers
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require ecotone/ecotone
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Laravel and Symfony integrations available.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Legacy codebases are not failures — they are assets.&lt;br&gt;
Incremental modernization gives them a future.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>codequality</category>
      <category>php</category>
    </item>
    <item>
      <title>Implementing Event-Driven Architecture in PHP</title>
      <dc:creator>Dariusz Gafka</dc:creator>
      <pubDate>Fri, 26 Dec 2025 16:42:28 +0000</pubDate>
      <link>https://dev.to/dgafka/implementing-event-driven-architecture-in-php-27ho</link>
      <guid>https://dev.to/dgafka/implementing-event-driven-architecture-in-php-27ho</guid>
      <description>&lt;p&gt;Traditional service integration moves routing logic outside the application’s code.&lt;br&gt;
Message brokers, cloud messaging services, and stream-processing topologies become the place where business-critical flows are defined.&lt;/p&gt;

&lt;p&gt;However, this comes at a cost of making our Endpoints - Dumb.&lt;/p&gt;
&lt;h2&gt;
  
  
  Dumb Endpoints
&lt;/h2&gt;

&lt;p&gt;We often agree to move routing logic outside the application, because it promises simplicity or speed. It looks simpler because it looks like we no longer need to handle routing ourselves. From a developer’s perspective, we just receive and process a message, while routing happens “somewhere else” — outside the code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When important logic is pushed outside the application, the code becomes unaware of the integrations it depends on. This makes changes harder to test and verify. It also lowers confidence when making changes, because modifying something outside the application is always riskier than changing code we fully own and can easily cover with tests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When we follow the dumb endpoints approach, where the application is unaware of routing logic, we eventually end up in a situation where:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Knowledge becomes fragmented&lt;/strong&gt; — Only a few people truly understand the full setup and configuration that lives outside the applications being integrated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testing becomes painful&lt;/strong&gt; — It is no longer easy to test behavior using automated application-level tests. Changes often require modifying external configurations, where testing and verifying correctness is much harder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Changes become risky&lt;/strong&gt; — When changes cannot be easily verified, confidence drops. This slows development and often leads to more bugs and production issues.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The state of the architecture is often accepted as it is, and the problems created by dumb endpoints are pushed onto developers. This often leads to situations where more “control” is introduced to prevent further issues — for example, by adding gatekeepers who must review and approve every change.&lt;br&gt;
Ironically, this all starts with the promise of speed and simplicity, offered as a trade-off for moving integration logic outside the application.&lt;/p&gt;

&lt;p&gt;However, we can achieve both speed and simplicity while keeping integrations under the control of the application itself. There is no trade-off required. To do this, we need to follow a different approach — one where &lt;strong&gt;endpoints are no longer dumb, but become smart.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Smart Endpoints - Dumb Pipes
&lt;/h2&gt;

&lt;p&gt;This leads us to the &lt;strong&gt;Smart Endpoints, Dumb Pipes&lt;/strong&gt; approach.&lt;br&gt;
It reverses the direction of responsibility — instead of moving logic outward, we move it back inward. Applications are no longer dumb. They become smart and decide where messages should go and where they should be consumed from. In this model, the application itself fully controls the integration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To achieve smart endpoints we need to build the logic of routing inside our Applications. This means using clear abstractions that allow us to orchestrate message flow within the code, rather than external configurations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To make this possible, messaging needs to be a first-class citizen in our applications.&lt;br&gt;
The messaging abstraction should provide routing capabilities that we can configure as needed and fully test from within the application. Ideally, this abstraction should be decoupled, meaning we are not forced to implement or extend any framework-specific classes.&lt;/p&gt;

&lt;p&gt;Enterprise Integration Patterns is a great book that defines a set of abstractions for building messaging systems at the programming-language level. I brought these patterns to life in the &lt;strong&gt;Ecotone Framework for PHP&lt;/strong&gt;.&lt;br&gt;
In the next section, we will explore how to build integrations between applications using a higher-level abstraction built on top of these patterns — the &lt;strong&gt;Service Map.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Service Map
&lt;/h2&gt;

&lt;p&gt;Now that we’ve established that smart endpoints keep routing logic inside the application and provide messaging capabilities directly within the programming language, let’s explore how applications can actually be integrated. &lt;br&gt;
To do this, we will look at one of Ecotone’s features — the Service Map.  &lt;/p&gt;

&lt;p&gt;Service Map is exactly what it sounds like—a map of integrated applications (services) and the pipes (channels) which they communicate through. Here's how to set it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="na"&gt;#[ServiceContext]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;serviceMap&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;DistributedServiceMap&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;DistributedServiceMap&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withCommandMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;targetServiceName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"ticketService"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;channelName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"ticket_commands"&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This configuration says:&lt;/strong&gt; "When sending Commands to Ticket Service, use ticket_commands channel (pipe)"&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The routing is done at the Application level, not the Message Broker level. This means that we control the process from within the codebase we own, and can easily cover that with tests.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;This configuration is for sending Commands, for Event we will be using Event Mapping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="na"&gt;#[ServiceContext]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;serviceMap&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;DistributedServiceMap&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;DistributedServiceMap&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withEventMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;channelName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"ticket_events"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;subscriptionKeys&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"user.*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration says: "When publishing Events, when routing key start with user then ticket_events channel (pipe)"&lt;br&gt;&lt;br&gt;
Event Mapping allows us to publish Events to specific Channel (Pipe) based on subscription keys. &lt;/p&gt;

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

&lt;p&gt;We can of course have multiple subscription to broadcast events to different Services. &lt;/p&gt;
&lt;h2&gt;
  
  
  Application Code
&lt;/h2&gt;

&lt;p&gt;With the map configured, publishing is straightforward. &lt;/p&gt;
&lt;h3&gt;
  
  
  Sending side
&lt;/h3&gt;

&lt;p&gt;For Commands, we target a specific service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;onUserRegistered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="kt"&gt;DistributedBus&lt;/span&gt; &lt;span class="nv"&gt;$distributedBus&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$distributedBus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;convertAndSendCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;targetServiceName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"ticketService"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;routingKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"ticket.create"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CreateTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Welcome!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Events that multiple services might care about, we publish without a target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$distributedBus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;convertAndPublishEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;routingKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"user.registered"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UserRegistered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Ecotone makes this part of the API: Commands are sent to a single service, while Events can be delivered to many services.&lt;br&gt;
The Service Map automatically handles routing based on subscription keys.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Receiving side
&lt;/h3&gt;

&lt;p&gt;On the receiving side, we mark handlers as distributed to accept external messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="na"&gt;#[Distributed]&lt;/span&gt;
&lt;span class="na"&gt;#[CommandHandler("ticket.create")]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;createTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;CreateTicket&lt;/span&gt; &lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create the ticket&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="na"&gt;#[Distributed]&lt;/span&gt;
&lt;span class="na"&gt;#[EventHandler("user.registered")]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;onUserRegistered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;UserRegistered&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// React to user registration&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;#[Distributed]&lt;/code&gt; attribute makes it explicit that these handlers can receive messages from other services. This clarity prevents accidental breaking changes.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I mentioned earlier that this approach does not require sacrificing speed.&lt;br&gt;
We are not building our own integration infrastructure from scratch — instead, we reuse existing systems.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The key idea is to keep the logic inside the application and treat pipes (channels) as simple transport.&lt;br&gt;
The channel’s only responsibility is to move messages, not to act as the “mastermind” of orchestration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We have two message channels (pipes): &lt;strong&gt;ticket_commands&lt;/strong&gt; and &lt;strong&gt;event_commands&lt;/strong&gt;.&lt;br&gt;
With the Service Map approach, we can define their implementations in a way that fits our needs, without being tightly coupled to a specific message broker.  &lt;/p&gt;

&lt;p&gt;This means we can choose — and later switch — the underlying technology if needed.&lt;br&gt;
For example, we might decide to use RabbitMQ or Redis-based channels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="na"&gt;#[ServiceContext]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;// Amazon SQS Message Channel&lt;/span&gt;
        &lt;span class="nc"&gt;SqsBackedMessageChannelBuilder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ticket_events"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="c1"&gt;// RabbitMQ Message Channel&lt;/span&gt;
        &lt;span class="nc"&gt;AmqpBackedMessageChannelBuilder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ticket_commands"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Defining Channel is enough for Ecotone to automatically register Message Consumer for us. From that point on, we can start consuming messages right away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/console ecotone:run &lt;span class="o"&gt;{&lt;/span&gt;ticket_commands/ticket_events&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Streaming Channels
&lt;/h2&gt;

&lt;p&gt;The Service Map works regardless of whether we use queue-based brokers or streaming platforms under the hood. &lt;br&gt;
When using streaming platforms, we gain additional capabilities thanks to their non-destructive nature, which I described in a &lt;a href="https://blog.ecotone.tech/async-failure-recovery-queue-vs-streaming-channel-strategies/" rel="noopener noreferrer"&gt;previous blog post&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;In the case of queue-based solutions, we can push messages to each channel as part of the publishing process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="na"&gt;#[ServiceContext]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;serviceMap&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;DistributedServiceMap&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;DistributedServiceMap&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withEventMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;channelName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"ticket_events"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;subscriptionKeys&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"user.*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withEventMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;channelName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"order_events"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;subscriptionKeys&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"user.*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5j6axwepidysvz5qk4kn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5j6axwepidysvz5qk4kn.png" alt="Queue based Event publishing" width="450" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When using Kafka or RabbitMQ streaming channels, we can push messages to a single channel, from which multiple services can consume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="na"&gt;#[ServiceContext]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;serviceMap&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;DistributedServiceMap&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;DistributedServiceMap&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withEventMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;channelName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"user_events"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;subscriptionKeys&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"user.*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ukpqsgbr2314lb6cyq2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ukpqsgbr2314lb6cyq2.png" alt="Streaming based Event Publishing" width="450" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ecotone provides different Message Channels integrations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Streaming Channels: Kafka and RabbitMQ&lt;/li&gt;
&lt;li&gt;Queue Channels: RabbitMQ, Amazon SQS, Redis, Database Channels, Symfony Messenger, Laravel Queues&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Decoupled Data Models
&lt;/h2&gt;

&lt;p&gt;All communication happens through defined routing keys, whether the message is a Command or an Event. This is intentional and helps keep applications decoupled from each other.  &lt;/p&gt;

&lt;p&gt;As a result, each application can use models that fit its own needs and include only the data that is truly meaningful from an integration perspective.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Publisher sends this&lt;/span&gt;
&lt;span class="nv"&gt;$distributedBus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;convertAndPublishEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;routingKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"user.billing.changed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BillingDetailsChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$newAddress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Consumer can use different model&lt;/span&gt;
&lt;span class="na"&gt;#[Distributed]&lt;/span&gt;
&lt;span class="na"&gt;#[EventHandler("user.billing.changed")]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;UserAddressUpdated&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Different class, same routing key&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Whether models are shared or not should be a project-level decision.&lt;br&gt;
Ecotone does not force either approach, allowing teams to choose what works best for their specific context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Testing Integrations
&lt;/h2&gt;

&lt;p&gt;One of the core ideas I mentioned earlier is making integrations testable at the application level. With Ecotone’s Service Map, we can test integrations using in-memory channels or real integrations, all directly from the application code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$messaging&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EcotoneLite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;bootstrapFlowTesting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;ServiceMapConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;enableAsynchronousProcessing&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;// Define using which Channel you want to test&lt;/span&gt;
        &lt;span class="nc"&gt;SimpleMessageChannelBuilder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;createQueueChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ticket_commands"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$messaging&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;convertAndSendCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;targetServiceName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"ticketService"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;routingKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"ticket.create"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CreateTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Welcome!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Verify command landed in channel&lt;/span&gt;
&lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$messaging&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessageChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ticket_commands'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertNotNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same way we could test out consumption side of things. It's really easy to test any kind of Service Map and cover that with automated tests to ensure that delivery happens as we expect. &lt;/p&gt;

&lt;h2&gt;
  
  
  Other Supporting Features
&lt;/h2&gt;

&lt;p&gt;We did cover the core part of integration, however together with that Ecotone provides much more features, that ensures that integrations works as expected. For this you may consider exploring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Outbox pattern: For transactional consistency&lt;/li&gt;
&lt;li&gt;Dead letter queues: For failed message handling &lt;/li&gt;
&lt;li&gt;Message priorities: For urgent processing &lt;/li&gt;
&lt;li&gt;Scheduled messages: For delayed delivery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this you may take a look on &lt;a href="https://docs.ecotone.tech/" rel="noopener noreferrer"&gt;Ecotone's documentation page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Choosing the &lt;strong&gt;Smart Endpoints, Dumb Pipes&lt;/strong&gt; architecture allows us to take full control of the integration process and keep things simple, testable, and easy to verify for everyone.&lt;br&gt;
The goal is to keep integration logic close to where it is actually used. This helps maintain shared knowledge and a clear understanding of how the system behaves as it evolves.&lt;/p&gt;

&lt;p&gt;You can read more about about Service Map under &lt;a href="https://docs.ecotone.tech/modelling/microservices-php/distributed-bus/distributed-bus-with-service-map" rel="noopener noreferrer"&gt;this link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Whatever you choose to use Ecotone to deliver this approach or build it yourself, feel free to join &lt;a href="https://discord.gg/GwM2BSuXeg" rel="noopener noreferrer"&gt;Ecotone's community channel&lt;/a&gt; to discuss different approaches and share the experiences. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>php</category>
      <category>eventdriven</category>
    </item>
  </channel>
</rss>
