.NET - Apresentando a arquitetura de Microsserviços - II
Neste artigo vamos iniciar a apresentação da arquitetura de Microsserviços e mostrar uma implementação na plataforma .NET |
A figura a seguir ilustra a arquitetura da aplicação que iremos criar :
Neste exemplo teremos 3 microsserviços representados por aplicações asp.net core web api definidos da seguinte forma:
Nestes
microsserviços, para simplificar, eu estou realizando o CRUD básico onde cada um
desses microsserviços será um projeto WebAPI independente, cada um com seus
próprios bancos de dados e hospedados em servidores diferentes.
Teremos tambem o projeto
Ecommerce-APIGateway
onde vamos
definir a API Gateway responsável por redirecionar a solicitação recebida do
cliente que vai atender na porta 7133.
E para isso vamos
usar o OCELOT.
A API Gateway é o ponto de entrada para clientes onde todas as chamadas do
cliente serão atendidas, analisadas e encaminhadas para os serviços apropriados.
Em vez de chamar cada microsserviço individualmente, vamos configurar o
APIGateway como um host e o cliente se conecta ao gateway, que por sua vez
redireciona os dados/endpoints de/para os microsserviços.
O Ocelot é um API
Gateway leve, recomendado para abordagens mais simples sendo uma API Gateway de
código aberto baseado em .NET Core feito especialmente para arquiteturas de
microsserviços que precisam de pontos de entrada unificados em seus sistemas. É
leve, rápido e escalonável e fornece roteamento e autenticação, entre muitos
outros recursos.
O principal motivo para escolher o Ocelot é porque ele é um API Gateway leve do
.NET Core que você pode implantar no mesmo ambiente de implantação do aplicativo
onde está implantando seus microsserviços/contêineres, como um Docker Host,
Kubernetes , etc. E como é baseado no .NET Core, é multiplataforma, permitindo a
implantação em Linux ou Windows.
No projeto BusinessObjects que é um projeto do tipo Class Library é onde definimos as entidades usadas no projeto : Customer, Order e Product.
E no projeto Ecommerce-APIGateway definimos as configurações dos serviços do OCELOT no arquivo apiGateway.json :
{
"GlobalConfiguration": {
"BaseUrl": "https://localhost:7133"
},
"Routes": [
{
// Products Microservice Route
"DownstreamPathTemplate": "/api/products",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 7089
}
],
"UpstreamPathTemplate": "/gateway/product",
"RateLimitOptions": {
"ClientWhiteList": [],
"EnableRateLimiting": true,
"Period": "1s",
"PeriodTimespan": 1,
"Limit": 1
},
"UpstreamHttpMethod": [ "POST", "PUT", "GET" ]
},
{
"DownstreamPathTemplate": "/api/products/{id}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 7089
}
],
"UpstreamPathTemplate": "/gateway/product/{id}",
"UpstreamHttpMethod": [ "GET", "DELETE" ]
},
// Orders Microservice Route
{
"DownstreamPathTemplate": "/api/orders",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 7147
}
],
"UpstreamPathTemplate": "/gateway/order",
"UpstreamHttpMethod": [ "POST", "PUT", "GET" ]
},
{
"DownstreamPathTemplate": "/api/orders/{id}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 7147
}
],
"UpstreamPathTemplate": "/gateway/order/{id}",
"UpstreamHttpMethod": [ "GET", "DELETE" ]
},
// Customers Microservice Route
{
"DownstreamPathTemplate": "/api/customers",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 7281
}
],
"UpstreamPathTemplate": "/gateway/customer",
"UpstreamHttpMethod": [ "POST", "PUT", "GET" ]
},
{
"DownstreamPathTemplate": "/api/customers/{id}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 7281
}
],
"UpstreamPathTemplate": "/gateway/customer/{id}",
"UpstreamHttpMethod": [ "GET", "DELETE" ]
}
]
}
|
O código acima define a configuração do Ocelot que define rotas para redirecionar solicitações para microsserviços individuais, neste caso, os microsserviços "Product", "Order" e "Customer".
Vejamos como funciona:
Neste projeto temos na classe Program a configuração para leitura do arquivo json e a utilização do cache para o OCELOT:
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using Ocelot.Cache.CacheManager;
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("apiGateway.json", optional: false,
reloadOnChange: true);
builder.Services.AddOcelot(builder.Configuration)
.AddCacheManager(x =>
{
x.WithDictionaryHandle();
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
await app.UseOcelot();
app.Run();
|
Microsserviços
Cada microsserviço vai possuir um arquivo de contexto do EF Core definindo o mapeamento ORM :
1- CustomerServiceContext
public class CustomerServiceContext : DbContext
{
public CustomerServiceContext(DbContextOptions<CustomerServiceContext> options)
: base(options)
{
}
public DbSet<BusinessObjects.Customer>? Customers { get; set; }
}
|
2- OrderServiceContext
public class OrderServiceContext : DbContext
{
public OrderServiceContext(DbContextOptions<OrderServiceContext> options)
: base(options)
{
}
public DbSet<BusinessObjects.Order>? Orders { get; set; }
}
|
3- ProductServiceContext
public class ProductServiceContext : DbContext
{
public ProductServiceContext(DbContextOptions<ProductServiceContext> options)
: base(options)
{
}
public DbSet<BusinessObjects.Product>? Products { get; set; }
}
|
A seguir temos a configuração da string de conexão no arquivo appsettings e do serviço do contexto na classe Program para cada microsserviço :
1- CustomerServiceContext - MySQL
appsettings.json
"ConnectionStrings":
{ "CustomerServiceContext": "Server=localhost;DataBase=CustomersDB;Uid=root;Pwd=*****" } ... |
Program:
string mySqlConnection = builder.Configuration.GetConnectionString("CustomerServiceContext")
?? throw new
InvalidOperationException("Connection string 'CustomerServiceContext' not found.");
builder.Services.AddDbContext<CustomerServiceContext>(options =>
options.UseMySql(mySqlConnection,
ServerVersion.AutoDetect(mySqlConnection)));
|
2- OrderServiceContext - SQLite
appsettings.json
"ConnectionStrings":
{ "OrderServiceContext": "Data Source=OrdersDB.db" } ... |
Program:
// Add services to the container.
builder.Services.AddDbContext<OrderServiceContext>(Options =>
{
Options.UseSqlite(builder.Configuration.GetConnectionString("OrderServiceContext"));
});
|
3- ProductServiceContext - SQL Server
appsettings.json
"ConnectionStrings":
{ "ProductServiceContext": "Data Source=;Database=ProductsDB;Trusted_Connection=True;MultipleActiveResultSets=true; TrustServerCertificate=True;" }... |
Program:
...
builder.Services.AddDbContext<ProductServiceContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("ProductServiceContext")
?? throw new InvalidOperationException("Connection string 'ProductServiceContext' not found.")));
...
|
Em cada projeto de cada microsserviços temos a definição de um controlador contendo os métodos Action para realizar as operações CRUD:
O código de cada controlador poderá ser consultado no código fonte pois foi gerado via scafold.
Para executar o projeto devemos configurar o modo Multiple startup projects conforme abaixo:
A figura a seguir ilustra o funcionamento da aplicação e da API Gateway implementada usando o OCELOT :
Pegue o código do projeto aqui: https://github.com/macoratti/Microservices-NET
"No dia seguinte João viu a Jesus, que vinha para ele,
e disse: Eis o Cordeiro de Deus, que tira o pecado do mundo."
João 1:29
Referências:
C# - Tasks x Threads. Qual a diferença
DateTime - Macoratti.net
Null o que é isso ? - Macoratti.net
Formatação de data e hora para uma cultura ...
C# - Calculando a diferença entre duas datas
NET - Padrão de Projeto - Null Object Pattern
C# - Fundamentos : Definindo DateTime como Null ...
C# - Os tipos Nullable (Tipos Anuláveis)