Characteristic Description
Single Responsibility Mỗi service chỉ làm một việc
Loose Coupling Services giao tiếp qua APIs
Independent Deploy Deploy không ảnh hưởng services khác
Technology Diversity Mỗi service có thể dùng công nghệ khác
Ownership Team sở hữu service từ dev đến production
┌─────────────────────────────────┐ ┌───────┐ ┌───────┐ ┌───────┐
│ MONOLITH │ │Order │ │Product│ │Customer│
│ ┌───────────────────────────┐ │ │Service│ │Service│ │Service │
│ │ UI │ Business │ Data │ │ │ └───┬───┘ └───┬───┘ └───┬───┘
│ └───────────────────────────┘ │ │ │ │
└─────────────────────────────────┘ └──────────┴──────────┘
┌─────────────────────────┐
│ API GATEWAY │
└─────────────────────────┘
dotnet add package Ocelot
{
"Routes": [
{
"DownstreamPathTemplate": "/api/products/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{ "Host": "product-service", "Port": 80 }
],
"UpstreamPathTemplate": "/products/{everything}",
"UpstreamHttpMethod": ["GET", "POST", "PUT", "DELETE"],
"RateLimitOptions": {
"ClientWhitelist": "",
"EnableRateLimiting": true,
"Period": "1s",
"PeriodTimespan": 1,
"Limit": 100
},
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer",
"AllowedScopes": []
}
},
{
"DownstreamPathTemplate": "/api/orders/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{ "Host": "order-service", "Port": 80 }
],
"UpstreamPathTemplate": "/orders/{everything}",
"UpstreamHttpMethod": ["GET", "POST", "PUT", "DELETE"]
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5000"
}
}
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOcelot();
var app = builder.Build();
await app.UseOcelot();
app.Run();
dotnet add package RabbitMQ.Client
public class OrderMessagePublisher
{
private readonly IConnection _connection;
private readonly IModel _channel;
public OrderMessagePublisher()
{
var factory = new ConnectionFactory
{
HostName = "rabbitmq",
UserName = "guest",
Password = "guest"
};
_connection = factory.CreateConnection();
_channel = _connection.CreateModel();
_channel.ExchangeDeclare("orders", ExchangeType.Direct, durable: true);
}
public void PublishOrderCreated(Order order)
{
var message = JsonSerializer.Serialize(order);
var body = Encoding.UTF8.GetBytes(message);
var properties = _channel.CreateBasicProperties();
properties.Persistent = true;
properties.ContentType = "application/json";
_channel.BasicPublish(
exchange: "orders",
routingKey: "order.created",
basicProperties: properties,
body: body);
}
}
public class OrderMessageConsumer : BackgroundService
{
private readonly IConnection _connection;
private readonly IModel _channel;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var factory = new ConnectionFactory
{
HostName = "rabbitmq",
UserName = "guest",
Password = "guest"
};
_connection = factory.CreateConnection();
_channel = _connection.CreateModel();
_channel.QueueDeclare("order.created", durable: true);
var consumer = new EventingBasicConsumer(_channel);
consumer.Received += async (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
var order = JsonSerializer.Deserialize<Order>(message);
await ProcessOrderAsync(order);
_channel.BasicAck(ea.DeliveryTag, false);
};
_channel.BasicConsume(
queue: "order.created",
autoAck: false,
consumer: consumer);
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(1000, stoppingToken);
}
}
}
dotnet add package Confluent.Kafka
var config = new ProducerConfig
{
BootstrapServers = "localhost:9092",
ClientId = "order-producer"
};
using var producer = new ProducerBuilder<string, string>(config).Build();
var message = new Message<string, string>
{
Key = order.Id.ToString(),
Value = JsonSerializer.Serialize(order)
};
var result = await producer.ProduceAsync("orders", message);
var config = new ConsumerConfig
{
BootstrapServers = "localhost:9092",
GroupId = "order-consumer",
AutoOffsetReset = AutoOffsetReset.Earliest
};
using var consumer = new ConsumerBuilder<string, string>(config).Build();
consumer.Subscribe("orders");
while (true)
{
var consumeResult = consumer.Consume();
var order = JsonSerializer.Deserialize<Order>(consumeResult.Message.Value);
await ProcessOrderAsync(order);
}
Aspect RabbitMQ Kafka
Protocol AMQP Binary (custom)
Use Case Task queues, simple messaging Event streaming, high throughput
Message Retention Per-queue (short-term) Topic-based (long-term)
Ordering Per-queue Per-partition
Scalability Horizontal Very high
Complexity Simpler More complex
Delivery Guarantee At-least-once, Exactly-once At-least-once, Exactly-once