Event-Driven Architecture (EDA) Implementation for Web Applications

Microservices grow, synchronous HTTP requests turn the system into a web. One failure — and the entire stack goes down. [Event-Driven Architecture (EDA)](https://en.wikipedia.org/wiki/Event-driven_architecture) is the only way to decouple this connectivity. We have implemented EDA for projects with

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1318
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1276
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1019
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1072
  • Website development for SBH Partners
    Website development for SBH Partners
    1136
  • Website development for Red Pear
    Website development for Red Pear
    575

Microservices grow, synchronous HTTP requests turn the system into a web. One failure — and the entire stack goes down. Event-Driven Architecture (EDA) is the only way to decouple this connectivity. We have implemented EDA for projects with loads up to 10,000 events per second and know how to avoid common mistakes.

A client with an online store processing 50,000 orders per day faced timeouts during peak loads, blocking inventory and notifications. After implementing EDA with Apache Kafka and the Outbox Pattern, response time dropped by 40%, throughput tripled, and support costs decreased by 30% (over $5,000 per month). Average event processing latency is 10ms.

Why EDA is better than synchronous interaction?

Synchronous HTTP calls are like phone conversations: you need an immediate answer. EDA works like mail: send a letter and forget. Comparison:

Parameter Synchronous Asynchronous (EDA)
Time dependency Client waits Client non-blocking
Fault tolerance Failure breaks chain Queue isolates failures
Load Direct RPS proportion Buffering + backpressure
Development complexity Easier to understand Harder debugging (idempotency, monitoring)

How we implement EDA: stack and case

We use Apache Kafka as the primary broker due to its high throughput and long-term storage.

Event structure

interface DomainEvent<T = unknown> { id: string; // UUID — for idempotency type: string; // 'user.registered', 'order.placed' version: string; // '1.0' — for schema evolution source: string; // 'order-service' correlationId: string; // cross-service correlation ID causationId?: string; // ID of the causing event occurredAt: string; // ISO 8601 data: T; } // Concrete event interface OrderPlacedEvent extends DomainEvent<{ orderId: string; customerId: string; items: Array<{ productId: string; quantity: number; price: number }>; total: number; shippingAddress: Address; }> { type: 'order.placed'; } 

Apache Kafka — primary broker

import { Kafka, Partitioners } from 'kafkajs'; const kafka = new Kafka({ clientId: 'order-service', brokers: process.env.KAFKA_BROKERS.split(',') }); // Producer const producer = kafka.producer({ createPartitioner: Partitioners.LegacyPartitioner }); async function publishOrderPlaced(order: Order): Promise<void> { await producer.send({ topic: 'order.events', messages: [{ key: order.id, // partitioning by order ID value: JSON.stringify({ id: uuidv4(), type: 'order.placed', version: '1.0', source: 'order-service', correlationId: context.correlationId, occurredAt: new Date().toISOString(), data: { orderId: order.id, customerId: order.customerId, items: order.items, total: order.total } } satisfies OrderPlacedEvent), headers: { 'content-type': 'application/json', 'schema-version': '1.0' } }] }); } 
// Consumer — Inventory Service const consumer = kafka.consumer({ groupId: 'inventory-service' }); await consumer.subscribe({ topics: ['order.events'], fromBeginning: false }); await consumer.run({ eachMessage: async ({ topic, partition, message }) => { const event = JSON.parse(message.value.toString()) as DomainEvent; // Idempotency: check if this event has already been processed const processed = await idempotencyRepo.exists(event.id); if (processed) return; try { if (event.type === 'order.placed') { await inventoryService.reserveStock(event.data.orderId, event.data.items); } await idempotencyRepo.mark(event.id); } catch (error) { // Publish to Dead Letter Topic for analysis await deadLetterProducer.send({ topic: 'order.events.dlq', messages: [{ value: message.value, headers: { 'failure-reason': error.message } }] }); } } }); 

Outbox Pattern — guaranteed delivery

Common mistake: save to DB then publish to Kafka — risk of event loss. Correct approach — Transactional Outbox:

// Within a single database transaction async function createOrder(dto: CreateOrderDto): Promise<Order> { return db.transaction(async (trx) => { // 1. Save order const order = await trx('orders').insert({ ...orderData }).returning('*'); // 2. Save event in outbox table (same transaction!) await trx('outbox_events').insert({ id: uuidv4(), aggregate_id: order.id, event_type: 'order.placed', payload: JSON.stringify(orderPlacedEvent), status: 'pending', created_at: new Date() }); return order; }); } 

A separate Outbox Poller reads pending events and publishes them to Kafka:

// Cron job or background worker async function processOutbox(): Promise<void> { const events = await db('outbox_events') .where({ status: 'pending' }) .orderBy('created_at') .limit(100) .forUpdate() .skipLocked(); for (const event of events) { try { await kafka.producer.send({ topic: getTopicForEventType(event.event_type), messages: [{ key: event.aggregate_id, value: event.payload }] }); await db('outbox_events') .where({ id: event.id }) .update({ status: 'published', published_at: new Date() }); } catch { await db('outbox_events') .where({ id: event.id }) .update({ retry_count: db.raw('retry_count + 1') }); } } } 

Alternative — Debezium CDC: reads PostgreSQL WAL and publishes changes to Kafka without code.

How to guarantee delivery without losses?

Key techniques:

  • Transactional Outbox
  • Consumer idempotency (check by event.id)
  • Dead Letter Queue for failed messages
  • Latency monitoring via Prometheus + Grafana

We set up a system handling 1000 msg/sec with no loss during a single Kafka node failure.

Efficient coordination: choreography vs orchestration

Characteristic Choreography Orchestration
Coordination Services react to events Central orchestrator
Coupling Low Medium
Flow visibility Hard to track Explicit in code
Testing Harder Easier

Choreography fits loosely coupled scenarios; orchestration when strict sequence is required. We combine: inside a service — orchestration, between services — choreography.

What are Event Sourcing and CQRS?

In Event Sourcing, all changes are stored as events, which are published to both event store and broker. CQRS (Command Query Responsibility Segregation) separates write and read operations, often using the same events to build projections. EDA and CQRS/ES work well together but require thoughtful design.

Example production Kafka configuration

For reliable production setup, configure replication factor 3, retention 7 days, compaction for critical topics. Minimum 3 brokers, monitor consumer lag via Burrow. For cloud environments — Confluent Cloud or AWS MSK.

What is included in the EDA implementation work?

When ordering, we provide:

  • Architectural documentation (diagrams, broker choice)
  • Development of producers and consumers with Outbox Pattern
  • Kafka/RabbitMQ setup with persistency and replication
  • Idempotency and Dead Letter Queue implementation
  • Load testing up to 1000 msg/sec without loss
  • Monitoring and alerting (latency, throughput)
  • Team training

Get a consultation on EDA implementation for your project — we will assess your architecture and propose an optimal solution.

How long does EDA implementation take?

Stage Duration
Single scenario (producer + 2–3 consumers) 1–2 weeks
+ Outbox Pattern, idempotency, DLQ +1 week
Full EDA for 5–10 services + monitoring 4–8 weeks

Broker choice depends on load and requirements. Kafka — for high throughput and long storage. RabbitMQ — for complex routing. Redis Streams — for low latency. Google Pub/Sub — for cloud. In most projects, we use Kafka.

EDA implementation in 4 steps

  1. Analysis and design: identify context boundaries, define events.
  2. Broker setup: deploy Kafka with replication, configure topics.
  3. Implement producers and consumers: write code with Outbox and idempotency.
  4. Monitoring and debugging: set up metrics, DLQ, alerts.

Our team has 10+ years of experience in distributed systems, with over 20 EDA projects. Order turnkey EDA implementation — get a reliable and scalable architecture. Contact us to evaluate your project.