Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Microservices

Nguyên tắc Microservice

Đặc điểm chính

CharacteristicDescription
Single ResponsibilityMỗi service chỉ làm một việc
Loose CouplingServices giao tiếp qua APIs
Independent DeployDeploy không ảnh hưởng services khác
Technology DiversityMỗi service có thể dùng công nghệ khác
OwnershipTeam sở hữu service từ dev đến production

Monolith vs Microservices

┌─────────────────────────────────┐     ┌───────┐  ┌───────┐  ┌───────┐
│          MONOLITH               │     │Order  │  │Product│  │Customer│
│  ┌───────────────────────────┐  │     │Service│  │Service│  │Service │
│  │ UI │ Business │ Data │    │  │     └───┬───┘  └───┬───┘  └───┬───┘
│  └───────────────────────────┘  │         │          │          │
└─────────────────────────────────┘         └──────────┴──────────┘
                                          ┌─────────────────────────┐
                                          │     API GATEWAY         │
                                          └─────────────────────────┘

API Gateway (Ocelot)

Cài đặt

dotnet add package Ocelot

Cấu hình ocelot.json

{
  "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"
  }
}

Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOcelot();

var app = builder.Build();

await app.UseOcelot();

app.Run();

Message Bus

RabbitMQ

Cài đặt

dotnet add package RabbitMQ.Client

Producer

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);
    }
}

Consumer

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);
        }
    }
}

Kafka

Cài đặt

dotnet add package Confluent.Kafka

Producer

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);

Consumer

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);
}

RabbitMQ vs Kafka

AspectRabbitMQKafka
ProtocolAMQPBinary (custom)
Use CaseTask queues, simple messagingEvent streaming, high throughput
Message RetentionPer-queue (short-term)Topic-based (long-term)
OrderingPer-queuePer-partition
ScalabilityHorizontalVery high
ComplexitySimplerMore complex
Delivery GuaranteeAt-least-once, Exactly-onceAt-least-once, Exactly-once