{"id":308,"date":"2026-04-23T06:25:58","date_gmt":"2026-04-23T06:25:58","guid":{"rendered":"https:\/\/magendoo.ro\/insights\/?p=308"},"modified":"2026-04-23T06:25:58","modified_gmt":"2026-04-23T06:25:58","slug":"event-driven-commerce-practical-patterns-with-go-workers","status":"publish","type":"post","link":"https:\/\/magendoo.ro\/insights\/event-driven-commerce-practical-patterns-with-go-workers\/","title":{"rendered":"Event-Driven Commerce: Practical Patterns with Go Workers"},"content":{"rendered":"<p>Event-driven is a philosophy. Go makes it practical. But most Magento teams that \u201cgo event-driven\u201d discover that adding a queue between systems isn\u2019t the hard part \u2014 handling failure, reprocessing, and consumer coordination is.<\/p>\n<h2 class=\"wp-block-heading\">Why Commerce Needs Event-Driven Architecture<\/h2>\n<p>Most Magento architectures start synchronous. An admin saves a product. The observer fires. The ERP gets updated via HTTP. The PIM gets notified. The search index rebuilds.<\/p>\n<p>Then the ERP is slow one morning. The HTTP timeout fires. The observer throws an exception. And suddenly saving a product in the admin panel is failing.<\/p>\n<p>The synchronous coupling is the problem. Not the observer pattern. Not Magento. The assumption that all connected systems must be available right now.<\/p>\n<p>Event-driven architecture breaks that assumption. When a product is updated, you publish an event. Go workers consume it. Systems get updated asynchronously, independently, and with retry logic that doesn\u2019t block the merchant.<\/p>\n<h2 class=\"wp-block-heading\">What \u201cEvent-Driven\u201d Actually Means in Commerce<\/h2>\n<p>It does not mean \u201cwe have RabbitMQ now.\u201d<\/p>\n<p>Event-driven is a boundary decision. You\u2019re saying: the source of truth publishes facts, and consumers react to those facts on their own schedule. Neither side knows \u2014 or cares \u2014 about the other\u2019s availability.<\/p>\n<p>In commerce, the relevant events are:<\/p>\n<ul>\n<li><strong>Catalog events<\/strong>: product created, updated, price changed, stock adjusted<\/li>\n<li><strong>Order events<\/strong>: order placed, payment captured, shipment updated, return initiated<\/li>\n<li><strong>Customer events<\/strong>: account created, loyalty tier changed, segment updated<\/li>\n<li><strong>Inventory events<\/strong>: warehouse stock updated, threshold breached, backorder triggered<\/li>\n<\/ul>\n<p>These events don\u2019t all need the same consumers. A price change event might feed a PIM system for record-keeping, a search index for faceted filtering, a pricing cache invalidation job, and a CDN purge for cached product pages.<\/p>\n<p>Each consumer runs independently, at its own pace, with its own retry strategy.<\/p>\n<h2 class=\"wp-block-heading\">Practical Go Worker Patterns<\/h2>\n<p>Go\u2019s concurrency model \u2014 goroutines and channels \u2014 makes building event consumers natural. But \u201cnatural\u201d doesn\u2019t mean \u201cautomatic.\u201d You still need to design for failure.<\/p>\n<h3 class=\"wp-block-heading\">Consumer Groups and Competing Consumers<\/h3>\n<p>If you have one worker consuming one queue, you have a single point of failure and a throughput ceiling.<\/p>\n<p>Consumer groups solve both problems. Multiple Go worker instances compete to consume from the same queue. Messages are distributed across workers. If one dies, others continue.<\/p>\n<p>With RabbitMQ, this means multiple consumers on the same queue. With Kafka, it means a consumer group where each partition is owned by one worker at a time. The semantics differ, but the principle is the same: horizontal scaling without coordination overhead.<\/p>\n<div class=\"sourceCode\" id=\"cb1\">\n<pre class=\"sourceCode go\"><code class=\"sourceCode go\"><span id=\"cb1-1\"><a href=\"#cb1-1\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"co\">\/\/ Worker pool \u2014 competing consumers, context-cancellable<\/span><\/span>\n<span id=\"cb1-2\"><a href=\"#cb1-2\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"kw\">func<\/span> startWorkerPool<span class=\"op\">(<\/span>ctx context<span class=\"op\">.<\/span>Context<span class=\"op\">,<\/span> queue Queue<span class=\"op\">,<\/span> workers <span class=\"dt\">int<\/span><span class=\"op\">)<\/span> <span class=\"op\">{<\/span><\/span>\n<span id=\"cb1-3\"><a href=\"#cb1-3\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"cf\">for<\/span> i <span class=\"op\">:=<\/span> <span class=\"dv\">0<\/span><span class=\"op\">;<\/span> i <span class=\"op\">&lt;<\/span> workers<span class=\"op\">;<\/span> i<span class=\"op\">++<\/span> <span class=\"op\">{<\/span><\/span>\n<span id=\"cb1-4\"><a href=\"#cb1-4\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>        <span class=\"cf\">go<\/span> <span class=\"kw\">func<\/span><span class=\"op\">()<\/span> <span class=\"op\">{<\/span><\/span>\n<span id=\"cb1-5\"><a href=\"#cb1-5\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>            <span class=\"cf\">for<\/span> <span class=\"op\">{<\/span><\/span>\n<span id=\"cb1-6\"><a href=\"#cb1-6\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>                <span class=\"cf\">select<\/span> <span class=\"op\">{<\/span><\/span>\n<span id=\"cb1-7\"><a href=\"#cb1-7\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>                <span class=\"cf\">case<\/span> msg <span class=\"op\">:=<\/span> <span class=\"op\">&lt;-<\/span>queue<span class=\"op\">.<\/span>Messages<span class=\"op\">():<\/span><\/span>\n<span id=\"cb1-8\"><a href=\"#cb1-8\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>                    processWithRetry<span class=\"op\">(<\/span>ctx<span class=\"op\">,<\/span> msg<span class=\"op\">)<\/span><\/span>\n<span id=\"cb1-9\"><a href=\"#cb1-9\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>                <span class=\"cf\">case<\/span> <span class=\"op\">&lt;-<\/span>ctx<span class=\"op\">.<\/span>Done<span class=\"op\">():<\/span><\/span>\n<span id=\"cb1-10\"><a href=\"#cb1-10\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>                    <span class=\"cf\">return<\/span><\/span>\n<span id=\"cb1-11\"><a href=\"#cb1-11\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>                <span class=\"op\">}<\/span><\/span>\n<span id=\"cb1-12\"><a href=\"#cb1-12\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>            <span class=\"op\">}<\/span><\/span>\n<span id=\"cb1-13\"><a href=\"#cb1-13\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>        <span class=\"op\">}()<\/span><\/span>\n<span id=\"cb1-14\"><a href=\"#cb1-14\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"op\">}<\/span><\/span>\n<span id=\"cb1-15\"><a href=\"#cb1-15\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"op\">}<\/span><\/span><\/code><\/pre>\n<\/div>\n<p>Scale workers based on queue depth, not wall-clock time. If your PIM sync is behind during flash sales, spin up more consumers \u2014 don\u2019t wait for the queue to drain overnight.<\/p>\n<h3 class=\"wp-block-heading\">Reprocessing and Idempotency<\/h3>\n<p>Messages will be delivered more than once. Network partitions, consumer crashes, acknowledgment failures \u2014 all of these cause redelivery.<\/p>\n<p>Your handlers must be idempotent. The same message processed twice must produce the same result.<\/p>\n<p>For a PIM sync worker consuming Magento product update events, idempotency means keying on <code>product_sku<\/code> + <code>updated_at<\/code>, ignoring messages with timestamps older than the last successful sync, and using upsert semantics rather than insert-or-fail.<\/p>\n<p>This isn\u2019t theoretical. In production, during a queue catch-up event (consumer down for 30 minutes, then restored), you will see thousands of duplicate deliveries. Systems that aren\u2019t idempotent produce duplicate data, corrupted records, or silent failures.<\/p>\n<h3 class=\"wp-block-heading\">Poison Messages<\/h3>\n<p>Some messages will always fail. The ERP is down. The schema changed. The product ID no longer exists.<\/p>\n<p>If you retry indefinitely, one bad message blocks your entire consumer. The queue backs up. Fresh events stop processing.<\/p>\n<p>Dead-letter queues (DLQs) are the answer. After N retries, move the message to a DLQ with the original payload, the error, and metadata. Your main consumer keeps moving. You process the DLQ separately \u2014 manually, or with a repair worker.<\/p>\n<div class=\"sourceCode\" id=\"cb2\">\n<pre class=\"sourceCode go\"><code class=\"sourceCode go\"><span id=\"cb2-1\"><a href=\"#cb2-1\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"kw\">func<\/span> processWithRetry<span class=\"op\">(<\/span>ctx context<span class=\"op\">.<\/span>Context<span class=\"op\">,<\/span> msg Message<span class=\"op\">)<\/span> <span class=\"op\">{<\/span><\/span>\n<span id=\"cb2-2\"><a href=\"#cb2-2\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"kw\">var<\/span> lastErr <span class=\"dt\">error<\/span><\/span>\n<span id=\"cb2-3\"><a href=\"#cb2-3\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"cf\">for<\/span> attempt <span class=\"op\">:=<\/span> <span class=\"dv\">0<\/span><span class=\"op\">;<\/span> attempt <span class=\"op\">&lt;<\/span> maxRetries<span class=\"op\">;<\/span> attempt<span class=\"op\">++<\/span> <span class=\"op\">{<\/span><\/span>\n<span id=\"cb2-4\"><a href=\"#cb2-4\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>        <span class=\"cf\">if<\/span> err <span class=\"op\">:=<\/span> handler<span class=\"op\">.<\/span>Process<span class=\"op\">(<\/span>ctx<span class=\"op\">,<\/span> msg<span class=\"op\">);<\/span> err <span class=\"op\">!=<\/span> <span class=\"ot\">nil<\/span> <span class=\"op\">{<\/span><\/span>\n<span id=\"cb2-5\"><a href=\"#cb2-5\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>            lastErr <span class=\"op\">=<\/span> err<\/span>\n<span id=\"cb2-6\"><a href=\"#cb2-6\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>            backoff <span class=\"op\">:=<\/span> time<span class=\"op\">.<\/span>Duration<span class=\"op\">(<\/span>attempt<span class=\"op\">*<\/span>attempt<span class=\"op\">)<\/span> <span class=\"op\">*<\/span> time<span class=\"op\">.<\/span>Second<\/span>\n<span id=\"cb2-7\"><a href=\"#cb2-7\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>            time<span class=\"op\">.<\/span>Sleep<span class=\"op\">(<\/span>backoff<span class=\"op\">)<\/span><\/span>\n<span id=\"cb2-8\"><a href=\"#cb2-8\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>            <span class=\"cf\">continue<\/span><\/span>\n<span id=\"cb2-9\"><a href=\"#cb2-9\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>        <span class=\"op\">}<\/span><\/span>\n<span id=\"cb2-10\"><a href=\"#cb2-10\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>        <span class=\"cf\">return<\/span> <span class=\"co\">\/\/ success<\/span><\/span>\n<span id=\"cb2-11\"><a href=\"#cb2-11\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"op\">}<\/span><\/span>\n<span id=\"cb2-12\"><a href=\"#cb2-12\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"co\">\/\/ All retries exhausted \u2014 send to DLQ<\/span><\/span>\n<span id=\"cb2-13\"><a href=\"#cb2-13\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    dlq<span class=\"op\">.<\/span>Publish<span class=\"op\">(<\/span>ctx<span class=\"op\">,<\/span> DeadLetter<span class=\"op\">{<\/span><\/span>\n<span id=\"cb2-14\"><a href=\"#cb2-14\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>        OriginalMessage<span class=\"op\">:<\/span> msg<span class=\"op\">,<\/span><\/span>\n<span id=\"cb2-15\"><a href=\"#cb2-15\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>        Error<span class=\"op\">:<\/span>           lastErr<span class=\"op\">.<\/span>Error<span class=\"op\">(),<\/span><\/span>\n<span id=\"cb2-16\"><a href=\"#cb2-16\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>        Attempts<span class=\"op\">:<\/span>        maxRetries<span class=\"op\">,<\/span><\/span>\n<span id=\"cb2-17\"><a href=\"#cb2-17\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>        FailedAt<span class=\"op\">:<\/span>        time<span class=\"op\">.<\/span>Now<span class=\"op\">(),<\/span><\/span>\n<span id=\"cb2-18\"><a href=\"#cb2-18\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"op\">})<\/span><\/span>\n<span id=\"cb2-19\"><a href=\"#cb2-19\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"op\">}<\/span><\/span><\/code><\/pre>\n<\/div>\n<p>Exponential backoff. Bounded retries. DLQ for human inspection. This is non-negotiable in production.<\/p>\n<h2 class=\"wp-block-heading\">Real Commerce Use Cases<\/h2>\n<h3 class=\"wp-block-heading\">PIM \u2192 Magento Sync<\/h3>\n<p>Your PIM is the source of truth for product content. Magento is the publishing target.<\/p>\n<p>When a product editor updates content in the PIM, an event fires. A Go worker consumes it, transforms the PIM schema to Magento\u2019s attribute structure, and calls the Magento REST API via <a href=\"https:\/\/github.com\/florinel-chis\/go-m2rest\">go-m2rest<\/a>.<\/p>\n<p>The worker handles rate limiting (Magento\u2019s API has limits), retry on 500\/503 responses (auto-retry is built into go-m2rest), conflict resolution based on timestamps, and DLQ routing for schema mismatches.<\/p>\n<p>The PIM doesn\u2019t know Magento exists. Magento doesn\u2019t know the PIM exists. They communicate via events and a shared schema contract.<\/p>\n<h3 class=\"wp-block-heading\">CRM Sync on Order Events<\/h3>\n<p>When an order is placed, customer behavior data flows to your CRM. New customer? Create a contact. Repeat purchase? Update the loyalty score. High-value order? Trigger a sales notification.<\/p>\n<p>A Go worker consumes <code>order.placed<\/code> events and fans out to multiple CRM operations. Use a message broker topic (Kafka) or exchange (RabbitMQ) to route events to multiple consumer groups.<\/p>\n<p>Each consumer group is independent:<\/p>\n<ul>\n<li>CRM sync worker \u2014 loyalty score update<\/li>\n<li>Fraud detection worker \u2014 risk scoring<\/li>\n<li>Email marketing worker \u2014 post-purchase flow trigger<\/li>\n<\/ul>\n<p>One event, three independent systems, zero coupling.<\/p>\n<h3 class=\"wp-block-heading\">ERP Stock Sync<\/h3>\n<p>ERP systems push stock updates \u2014 sometimes batch, sometimes near-real-time. Either way, Magento needs to reflect current inventory.<\/p>\n<p>A Go worker consumes stock update events and calls Magento\u2019s inventory API. The ordering challenge is real: if you receive two updates for the same SKU out of order, you might set stock to an older value.<\/p>\n<p>Use sequence numbers or timestamps in the event payload. Skip updates that are older than the last processed version for that SKU. <a href=\"https:\/\/github.com\/florinel-chis\/tracking-updater\">tracking-updater<\/a> uses a similar pattern \u2014 file lifecycle management to prevent reprocessing, worker pool concurrency, retry with backoff.<\/p>\n<h2 class=\"wp-block-heading\">The Magento Side of This<\/h2>\n<p>Magento generates events through several mechanisms:<\/p>\n<ul>\n<li><strong>Observers<\/strong>: <code>catalog_product_save_after<\/code>, <code>sales_order_place_after<\/code>, <code>customer_register_success<\/code><\/li>\n<li><strong>Message Queue<\/strong>: Magento 2 has a built-in message queue framework (<code>MessageQueue<\/code> module) with AMQP support<\/li>\n<li><strong>REST API<\/strong>: Magento endpoints that Go workers call to push data back in<\/li>\n<\/ul>\n<p>For event-driven Go workers, you have two integration points:<\/p>\n<p><strong>Option 1 \u2014 Magento emits directly<\/strong>: Configure a message queue publisher in <code>communication.xml<\/code> and have Magento publish events to RabbitMQ. Go workers subscribe.<\/p>\n<p><strong>Option 2 \u2014 A bridge service<\/strong>: An observer calls a lightweight HTTP endpoint on a Go bridge service, which translates to queue messages. Useful when you want to keep Magento\u2019s queue configuration simple and handle routing logic in Go.<\/p>\n<p>Either way, Magento doesn\u2019t run the consumers. It publishes facts. The boundary is clean.<\/p>\n<h2 class=\"wp-block-heading\">Decision Framework<\/h2>\n<p><strong>Use event-driven Go workers when:<\/strong><\/p>\n<ul>\n<li>Processing load would block Magento request threads if done synchronously<\/li>\n<li>Downstream systems are unreliable or slow (ERP, legacy CRM)<\/li>\n<li>You need fanout \u2014 one event, multiple consumers<\/li>\n<li>Retry and backoff logic would be complex inside a Magento observer<\/li>\n<li>You\u2019re syncing large data volumes (catalog imports, inventory batch updates)<\/li>\n<\/ul>\n<p><strong>Don\u2019t use event-driven when:<\/strong><\/p>\n<ul>\n<li>The operation is fast, local, and always available (cache invalidation within Magento)<\/li>\n<li>You need synchronous confirmation before responding to the user<\/li>\n<li>The operational complexity isn\u2019t justified by the actual load<\/li>\n<li>Your team doesn\u2019t have capacity to operate a message broker in production<\/li>\n<\/ul>\n<p>That last point matters more than the technical ones. A RabbitMQ cluster in production requires monitoring, backpressure management, and DLQ review processes. If you don\u2019t have that operational capacity, a well-built synchronous queue with database-backed retries is a more honest choice.<\/p>\n<h2 class=\"wp-block-heading\">The Leadership Angle<\/h2>\n<p>Event-driven architecture changes your operational model \u2014 and not everyone is ready for that.<\/p>\n<p>Synchronous systems fail loudly. API call fails, error surfaces, someone gets paged.<\/p>\n<p>Async systems fail silently \u2014 if you\u2019re not watching. A consumer that\u2019s been down for four hours doesn\u2019t page anyone. It just accumulates a backlog. And when it recovers, it processes 10,000 messages at once, potentially overwhelming downstream systems.<\/p>\n<p>You need:<\/p>\n<ul>\n<li><strong>Queue depth monitoring<\/strong> \u2014 alert when lag exceeds a threshold, not just when consumers crash<\/li>\n<li><strong>Consumer health checks<\/strong> \u2014 track active workers, restart on failure, alert on zero consumers<\/li>\n<li><strong>DLQ visibility<\/strong> \u2014 a dashboard showing messages that need human attention<\/li>\n<li><strong>Backpressure handling<\/strong> \u2014 rate-limit Go workers to protect downstream APIs from recovery floods<\/li>\n<\/ul>\n<p>The teams I\u2019ve seen succeed with this architecture invest in the observability layer before they need it. The teams I\u2019ve seen fail skip monitoring because they\u2019re in a hurry, then spend the next sprint firefighting through their first load event.<\/p>\n<p>Go makes event-driven practical. But \u201cpractical\u201d means you\u2019ve thought through failure modes, not just the happy path.<\/p>\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n<p>Event-driven is not an architecture upgrade you enable by adding a queue. It\u2019s a commitment to asynchronous thinking \u2014 about failure, ordering, idempotency, and consumer coordination.<\/p>\n<p>Go\u2019s concurrency model is genuinely well-suited for this. Worker pools, channel-based fanout, context cancellation for graceful shutdown \u2014 these patterns translate directly to real commerce problems.<\/p>\n<p>But the value comes from the discipline, not the language. A Go worker without DLQ, without idempotency, without monitoring is just a faster way to lose messages silently.<\/p>\n<p>Build the observability first. Then build the consumers. Then scale.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Event-driven is a philosophy. Go makes it practical. But most Magento teams that \u201cgo event-driven\u201d discover that adding a queue between systems isn\u2019t the hard part \u2014 handling failure, reprocessing, and consumer coordination is. Why Commerce Needs Event-Driven Architecture Most Magento architectures start synchronous. An admin saves a product. The observer fires. The ERP gets [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":307,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-container-style":"default","site-container-layout":"default","site-sidebar-layout":"default","disable-article-header":"default","disable-site-header":"default","disable-site-footer":"default","disable-content-area-spacing":"default","footnotes":""},"categories":[1],"tags":[],"class_list":["post-308","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-general"],"_links":{"self":[{"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/posts\/308","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/comments?post=308"}],"version-history":[{"count":1,"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/posts\/308\/revisions"}],"predecessor-version":[{"id":309,"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/posts\/308\/revisions\/309"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/media\/307"}],"wp:attachment":[{"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/media?parent=308"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/categories?post=308"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/magendoo.ro\/insights\/wp-json\/wp\/v2\/tags?post=308"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}