신뢰할 수 있는 이벤트 기반 시스템 구축: 이벤트 스키마, 버전 관리, 계약 테스트 및 이벤트 vs 명령 (파트 4)

작성자

카테고리:

← 피드로
DEV Community · Venkatesan Ramar · 2026-07-20 개발(SW)

In this article, we’re going to explore Contract Testing

Contract testing ensures that event contracts remain stable as systems evolve. Producers receive immediate feedback when changes affect consumers, and consumers gain confidence in the data they process.

16. Why Contract Testing Matters

Events act as public contracts, and those contracts evolve as systems grow. The key question is how a producer can verify that a schema change does not break its consumers.

Many teams rely on integration testing for this purpose. While useful, integration tests only confirm that systems can communicate. They do not guarantee that all consumers still understand the event contract.

Consider an Order Service publishing an OrderConfirmed event consumed by multiple services:

                  OrderConfirmed
                         |
      +------------------+-------------------+
      |                  |                   |
      v                  v                   v
 Inventory          Billing          Notification

Enter fullscreen mode Exit fullscreen mode

If the producer renames a field:
{
"customerId": "CUS-501"
}

to:
{
"accountId": "CUS-501"
}

everything may still appear correct. The code compiles, tests pass, and deployment succeeds. However, the Billing Service may fail at runtime because it still expects customerId. The producer had no visibility into this dependency.

The issue is not deployment failure but contract violation. Contract testing exists to detect such issues before they reach production.

  • Integration Tests Cannot Protect Unknown Consumers

Integration tests typically validate interactions between known systems:

Order Service
      |
      v
Inventory Service

Enter fullscreen mode Exit fullscreen mode

In this setup, both systems are controlled and predictable. Event-driven systems differ because consumers are loosely coupled and may not be known to the producer.

A new Analytics Service might start consuming OrderConfirmed months later. The producer cannot manually validate every consumer. Contract testing addresses this by validating expectations rather than communication.

17. Consumer-Driven Contracts

Traditional API testing starts with the producer defining the contract. Consumers adapt to it. Consumer-driven contract testing reverses this approach.

Consumers define their expectations, and producers verify that they continue to meet them. This model works well in event-driven systems where consumers evolve independently.

  • Thinking From the Consumer’s Perspective

Different consumers often require different subsets of data. For example, the Billing Service may need:

{
    "orderId": "ORD-1001",
    "customerId": "CUS-501",
    "totalAmount": 249.99
}

Enter fullscreen mode Exit fullscreen mode

The Notification Service may require:

{
    "orderId": "ORD-1001",
    "customerId": "CUS-501",
    "customerEmail": "[email protected]"
}

Enter fullscreen mode Exit fullscreen mode

The producer does not need to understand each consumer’s logic. It only needs to satisfy their declared contracts. This encourages careful evolution and explicit documentation of expectations.

  • A Contract Is More Than Field Names

Contracts define more than structure. They also capture meaning and constraints.

For example:
{
"orderId": "ORD-1001",
"totalAmount": 249.99
}

A robust contract may specify:
orderId must exist and not be empty
totalAmount must be numeric
totalAmount must be greater than zero

These rules ensure data integrity and provide stronger guarantees for both producers and consumers.

18. Contract Testing in Practice

Consider an Order Service publishing:

{
    "eventType": "OrderConfirmed",
    "eventVersion": "1.0",
    "payload": {
        "orderId": "ORD-1001",
        "customerId": "CUS-501",
        "currency": "USD",
        "totalAmount": 249.99
    }
}

Enter fullscreen mode Exit fullscreen mode

Consumers depend on different fields:
Billing: orderId, customerId, totalAmount
Inventory: orderId
Notification: customerId

If the producer changes the schema:

{
    "payload": {
        "orderId": "ORD-1001",
        "accountId": "CUS-501",
        "currency": "USD",
        "totalAmount": 249.99
    }
}

Enter fullscreen mode Exit fullscreen mode

the system may still compile and deploy. However, contract validation fails because consumer expectations are no longer met. The issue is detected before release, preventing runtime failures.

  • The Development Workflow

Contract testing integrates into the delivery pipeline:

Consumer Defines Contract
          |
          v
Producer Validates Contract
          |
          v
   Build Succeeds
          |
          v
       Deploy

Enter fullscreen mode Exit fullscreen mode

Every schema change is validated during the build process, ensuring compatibility before deployment.

19. Using JSON Schema to Describe Events

JSON Schema provides a structured way to define event contracts. It acts as an executable specification shared by producers and consumers.

Consider a sample schema:

{
  "type": "object",
  "required": [
    "orderId",
    "customerId",
    "totalAmount"
  ],
  "properties": {
    "orderId": {
      "type": "string"
    },
    "customerId": {
      "type": "string"
    },
    "totalAmount": {
      "type": "number"
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

This schema defines required fields, types, and structure. Additional constraints can include string lengths, numeric ranges, formats, and enumerations. Unlike static documentation, schemas can be validated automatically.

  • Validation During Publishing

Producers can validate events before publishing:

OrderConfirmedEvent event = ...

validator.validate(event);

eventPublisher.publish(event);

Enter fullscreen mode Exit fullscreen mode

Invalid events are rejected early, preventing faulty data from entering the system.

  • Validation During Consumption

Consumers can also validate incoming events:

OrderConfirmedEvent event = ...

validator.validate(event);

process(event);

Enter fullscreen mode Exit fullscreen mode

This ensures that only valid data reaches business logic, improving reliability.

20. Schema Registries and Centralized Contracts

As systems scale, managing schemas across multiple repositories becomes difficult. Teams may define similar events differently, leading to inconsistencies.

A centralized schema repository addresses this:

              Event Schemas
                    |
       +------------+------------+
       |                         |
       v                         v
 Producers                  Consumers

Enter fullscreen mode Exit fullscreen mode

This repository becomes the single source of truth. Producers publish against registered schemas, and consumers validate against them.

  • Why Centralized Schemas Help

Centralized schema management improves consistency and visibility. Contracts become discoverable, version history is preserved, and compatibility rules can be enforced automatically.

Teams spend less time interpreting payloads and more time building features. The contract becomes a shared organizational asset rather than an isolated implementation detail.

21. Common Contract Testing Mistakes

Several common issues arise when adopting contract testing.

  • Treating Documentation as the Contract

Documentation often becomes outdated as implementations change. Consumers may rely on incorrect assumptions. Executable contracts remain synchronized with the system and provide reliable validation.

  • Testing Only Happy Paths

Contracts should cover more than valid scenarios. They should include:

  • missing required fields
  • invalid data types
  • unexpected values
  • optional fields
  • deprecated fields

This ensures consumers handle both valid and invalid inputs correctly.

  • Ignoring Backward Compatibility

Passing current contracts does not guarantee future compatibility. Schema evolution must be validated alongside contract correctness to avoid breaking existing consumers.

  • Assuming Producers Own the Contract

Although producers publish events, contracts are shared responsibilities. Both producers and consumers must maintain and validate them.

Reliable event-driven systems depend on well-defined, continuously validated contracts.

In the next part, we will explore production practices like idempotency, event ordering, duplicate handling, correlation and observability.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다