| name | asyncapi-design |
| description | Event-driven API specification with AsyncAPI 3.0 for message-based architectures |
| allowed-tools | Read, Glob, Grep, Write, Edit, mcp__perplexity__search, mcp__context7__resolve-library-id, mcp__context7__query-docs |
AsyncAPI Design Skill
When to Use This Skill
Use this skill when:
- Asyncapi Design tasks - Working on event-driven api specification with asyncapi 3.0 for message-based architectures
- Planning or design - Need guidance on Asyncapi Design approaches
- Best practices - Want to follow established patterns and standards
Overview
Event-driven API specification using AsyncAPI 3.0 for message-based and streaming architectures.
MANDATORY: Documentation-First Approach
Before creating AsyncAPI specifications:
- Invoke
docs-management skill for event-driven patterns
- Verify AsyncAPI 3.0 syntax via MCP servers (context7 for latest spec)
- Base all guidance on AsyncAPI 3.0 specification
AsyncAPI vs OpenAPI
| Aspect | OpenAPI | AsyncAPI |
|---|
| Communication | Request/Response | Event-Driven |
| Protocol | HTTP/HTTPS | Kafka, RabbitMQ, MQTT, WebSocket, etc. |
| Initiator | Client requests | Publisher emits |
| Pattern | Synchronous | Asynchronous |
| Use Case | REST APIs | Message queues, streaming, IoT |
AsyncAPI 3.0 Structure
Basic Template
asyncapi: 3.0.0
info:
title: Order Events API
version: 1.0.0
description: |
Event-driven API for order lifecycle events.
This API publishes events when orders change state, enabling
downstream systems to react to order lifecycle changes.
contact:
name: Events Team
email: events@example.com
license:
name: MIT
url: https://opensource.org/licenses/MIT
servers:
production:
host: kafka.example.com:9092
protocol: kafka
description: Production Kafka cluster
security:
- $ref: '#/components/securitySchemes/sasl'
Protocol-Specific Patterns
Kafka
servers:
kafka:
host: broker1.example.com:9092,broker2.example.com:9092
protocol: kafka
protocolVersion: '3.0'
bindings:
kafka:
schemaRegistryUrl: http://schema-registry:8081
schemaRegistryVendor: confluent
channels:
orderEvents:
address: orders.events.v1
bindings:
kafka:
topic: orders.events.v1
partitions: 12
replicas: 3
topicConfiguration:
cleanup.policy: ['delete']
retention.ms: 604800000
segment.bytes: 1073741824
operations:
publishOrderEvent:
action: send
channel:
$ref: '#/channels/orderEvents'
bindings:
kafka:
groupId:
type: string
clientId:
type: string
bindingVersion: '0.5.0'
RabbitMQ
servers:
rabbitmq:
host: rabbitmq.example.com:5672
protocol: amqp
protocolVersion: '0.9.1'
channels:
orderQueue:
address: order-processing-queue
bindings:
amqp:
is: queue
queue:
name: order-processing
durable: true
exclusive: false
autoDelete: false
exchange:
name: orders-exchange
type: topic
durable: true
bindingVersion: '0.3.0'
MQTT (IoT)
servers:
mqtt:
host: mqtt.example.com:1883
protocol: mqtt
protocolVersion: '5.0'
channels:
deviceTelemetry:
address: devices/{deviceId}/telemetry
parameters:
deviceId:
description: Unique device identifier
schema:
type: string
bindings:
mqtt:
qos: 1
retain: false
bindingVersion: '0.2.0'
WebSocket
servers:
websocket:
host: ws.example.com
protocol: ws
protocolVersion: '13'
channels:
orderUpdates:
address: /orders/updates
bindings:
ws:
method: GET
headers:
type: object
properties:
Authorization:
type: string
C# Implementation Patterns
Event Contracts
public abstract record DomainEvent
{
public Guid EventId { get; init; } = Guid.NewGuid();
public DateTimeOffset OccurredAt { get; init; } = DateTimeOffset.UtcNow;
public string EventType => GetType().Name;
public string EventVersion { get; init; } = "1.0";
}
public sealed record OrderCreatedEvent(
Guid OrderId,
Guid CustomerId,
IReadOnlyList<LineItemDto> Items,
DateTimeOffset CreatedAt) : DomainEvent;
public sealed record OrderSubmittedEvent(
Guid OrderId,
Guid CustomerId,
Money Total,
DateTimeOffset SubmittedAt) : DomainEvent;
public sealed record OrderStatusChangedEvent(
Guid OrderId,
OrderStatus PreviousStatus,
OrderStatus NewStatus,
string? Reason,
DateTimeOffset ChangedAt) : DomainEvent;
public abstract record IntegrationEvent
{
public Guid Id { get; init; } = Guid.NewGuid();
public Guid CorrelationId { get; ; }
DateTimeOffset Timestamp { ; ; } = DateTimeOffset.UtcNow;
Source { ; ; } = ;
}
;
MassTransit Publisher
using MassTransit;
public sealed class OrderService
{
private readonly IPublishEndpoint _publishEndpoint;
private readonly ILogger<OrderService> _logger;
public OrderService(
IPublishEndpoint publishEndpoint,
ILogger<OrderService> logger)
{
_publishEndpoint = publishEndpoint;
_logger = logger;
}
public async Task CreateOrderAsync(
CreateOrderCommand command,
CancellationToken ct = default)
{
var order = Order.Create(command.CustomerId, command.Items);
await _publishEndpoint.Publish(
new OrderCreatedEvent(
order.Id,
order.CustomerId,
order.Items.Select(i => i.ToDto()).ToList(),
order.CreatedAt),
ct);
_logger.LogInformation(
"Published OrderCreatedEvent for order {OrderId}",
order.Id);
}
}
public sealed class OrderCreatedConsumer : IConsumer<OrderCreatedEvent>
{
private readonly ILogger<OrderCreatedConsumer> _logger;
public OrderCreatedConsumer(ILogger<OrderCreatedConsumer> logger)
{
_logger = logger;
}
public async Task ()
{
@event = context.Message;
_logger.LogInformation(
,
@event.OrderId,
@event.CustomerId);
}
}
services.AddMassTransit(x =>
{
x.AddConsumer<OrderCreatedConsumer>();
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host(, h =>
{
h.Username();
h.Password();
});
cfg.ConfigureEndpoints(context);
});
});
Kafka with Confluent
using Confluent.Kafka;
using System.Text.Json;
public sealed class KafkaOrderPublisher : IAsyncDisposable
{
private readonly IProducer<string, string> _producer;
private readonly string _topic;
private readonly ILogger<KafkaOrderPublisher> _logger;
public KafkaOrderPublisher(
IConfiguration config,
ILogger<KafkaOrderPublisher> logger)
{
_logger = logger;
_topic = config["Kafka:OrdersTopic"] ?? "orders.events.v1";
var producerConfig = new ProducerConfig
{
BootstrapServers = config["Kafka:BootstrapServers"],
Acks = Acks.All,
EnableIdempotence = true,
MessageSendMaxRetries = 3,
RetryBackoffMs = 1000
};
_producer = new ProducerBuilder<string, string>(producerConfig)
.SetKeySerializer(Serializers.Utf8)
.SetValueSerializer(Serializers.Utf8)
.Build();
}
public async Task PublishAsync<TEvent>(
TEvent @event,
CancellationToken ct = default) where TEvent : DomainEvent
{
var key = GetPartitionKey(@event);
var value = JsonSerializer.Serialize(@event);
message = Message<, >
{
Key = key,
Value = ,
Headers = Headers
{
{ , Encoding.UTF8.GetBytes(@event.EventType) },
{ , Encoding.UTF8.GetBytes(@event.EventVersion) },
{ , Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()) }
}
};
result = _producer.ProduceAsync(_topic, message, ct);
_logger.LogInformation(
,
@event.EventType,
result.Topic,
result.Partition.Value,
result.Offset.Value);
}
{
@event
{
OrderCreatedEvent e => e.OrderId.ToString(),
OrderSubmittedEvent e => e.OrderId.ToString(),
OrderStatusChangedEvent e => e.OrderId.ToString(),
_ => Guid.NewGuid().ToString()
};
}
{
_producer.Flush(TimeSpan.FromSeconds());
_producer.Dispose();
}
}
Event Design Patterns
Event Envelope Pattern
components:
schemas:
CloudEventEnvelope:
type: object
required:
- specversion
- type
- source
- id
- time
- data
properties:
specversion:
type: string
const: "1.0"
type:
type: string
description: Event type (e.g., com.example.order.created)
source:
type: string
format: uri
description: Event source URI
id:
type: string
format: uuid
time:
type: string
format: date-time
datacontenttype:
type: string
Event Versioning
channels:
orderEventsV1:
address: orders.events.v1
messages:
orderCreatedV1:
$ref: '#/components/messages/OrderCreatedV1'
orderEventsV2:
address: orders.events.v2
messages:
orderCreatedV2:
$ref: '#/components/messages/OrderCreatedV2'
components:
messages:
OrderCreatedV1:
name: OrderCreatedV1
schemaFormat: application/vnd.aai.asyncapi+json;version=3.0.0
payload:
type: object
properties:
orderId:
type: string
OrderCreatedV2:
name: OrderCreatedV2
schemaFormat: application/vnd.aai.asyncapi+json;version=3.0.0
payload:
type: object
properties:
orderId:
type: string
format: uuid
metadata:
type: object
Best Practices
Channel Naming
channels:
orderCreated:
address: orders.order.created.v1
orderItemAdded:
address: orders.lineitem.added.v1
paymentProcessed:
address: payments.payment.processed.v1
Message Design
- Self-describing: Include type and version in headers
- Idempotent: Use event ID for deduplication
- Ordered: Use partition keys for ordering
- Backward compatible: Add fields, don't remove
- Complete: Include all data consumers need (avoid chatty patterns)
Security Considerations
components:
securitySchemes:
oauth2:
type: oauth2
description: OAuth 2.0 authentication
flows:
clientCredentials:
tokenUrl: https://auth.example.com/oauth/token
scopes:
orders:read: Read order events
orders:write: Publish order events
mtls:
type: X509
description: Mutual TLS authentication
Workflow
When designing AsyncAPI specifications:
- Identify events: What significant occurrences need to be communicated?
- Define channels: What topics/queues will carry these events?
- Design messages: What data does each event contain?
- Choose protocol: Kafka, RabbitMQ, MQTT, etc.?
- Add bindings: Protocol-specific configuration
- Document security: Authentication and authorization
- Version strategy: How will events evolve?
- Generate code: Use AsyncAPI generator for clients/handlers
MCP Research
For current AsyncAPI patterns and tools:
perplexity: "AsyncAPI 3.0 specification" "event-driven API design patterns"
context7: "asyncapi" (for official documentation)
ref: "AsyncAPI spec examples" "Kafka binding patterns"
Last Updated: 2025-12-26