Usar bancos de dados NoSQL como uma infraestrutura de persistência
Gorjeta
Este conteúdo é um trecho do eBook, .NET Microservices Architecture for Containerized .NET Applications, disponível no .NET Docs ou como um PDF para download gratuito que pode ser lido offline.
Quando você usa bancos de dados NoSQL para sua camada de dados de infraestrutura, normalmente não usa um ORM como o Entity Framework Core. Em vez disso, você usa a API fornecida pelo mecanismo NoSQL, como Azure Cosmos DB, MongoDB, Cassandra, RavenDB, CouchDB ou Tabelas de Armazenamento do Azure.
No entanto, quando você usa um banco de dados NoSQL, especialmente um banco de dados orientado a documentos como o Azure Cosmos DB, CouchDB ou RavenDB, a maneira como você projeta seu modelo com agregações DDD é parcialmente semelhante à forma como você pode fazê-lo no EF Core, em relação à identificação de raízes agregadas, classes de entidade filho e classes de objeto de valor. Mas, em última análise, a seleção do banco de dados terá impacto no seu design.
Ao usar um banco de dados orientado a documentos, você implementa uma agregação como um único documento, serializado em JSON ou outro formato. No entanto, o uso do banco de dados é transparente do ponto de vista do código do modelo de domínio. Ao usar um banco de dados NoSQL, você ainda está usando classes de entidade e classes raiz agregadas, mas com mais flexibilidade do que ao usar o EF Core porque a persistência não é relacional.
A diferença está em como você persiste esse modelo. Se você implementou seu modelo de domínio com base em classes de entidade POCO, agnósticas à persistência da infraestrutura, pode parecer que você poderia mudar para uma infraestrutura de persistência diferente, mesmo de relacional para NoSQL. No entanto, esse não deve ser o seu objetivo. Há sempre restrições e compensações nas diferentes tecnologias de banco de dados, portanto, você não poderá ter o mesmo modelo para bancos de dados relacionais ou NoSQL. Alterar modelos de persistência não é uma tarefa trivial, porque as transações e as operações de persistência serão muito diferentes.
Por exemplo, em um banco de dados orientado a documentos, não há problema em uma raiz agregada ter várias propriedades de coleção filho. Em um banco de dados relacional, consultar várias propriedades de coleção filho não é facilmente otimizado, porque você obtém uma instrução UNION ALL SQL de volta do EF. Ter o mesmo modelo de domínio para bancos de dados relacionais ou bancos de dados NoSQL não é simples, e você não deve tentar fazê-lo. Você realmente tem que projetar seu modelo com uma compreensão de como os dados serão usados em cada banco de dados específico.
Um benefício ao usar bancos de dados NoSQL é que as entidades são mais desnormalizadas, portanto, você não define um mapeamento de tabela. Seu modelo de domínio pode ser mais flexível do que ao usar um banco de dados relacional.
Quando você projeta seu modelo de domínio com base em agregações, mover para NoSQL e bancos de dados orientados a documentos pode ser ainda mais fácil do que usar um banco de dados relacional, porque as agregações que você cria são semelhantes a documentos serializados em um banco de dados orientado a documentos. Então você pode incluir nesses "sacos" todas as informações que você pode precisar para esse agregado.
Por exemplo, o código JSON a seguir é uma implementação de exemplo de uma ordem agregada ao usar um banco de dados orientado a documentos. É semelhante ao agregado de pedidos que implementamos na amostra eShopOnContainers, mas sem usar o EF Core por baixo.
{
"id": "2024001",
"orderDate": "2/25/2024",
"buyerId": "1234567",
"address": [
{
"street": "100 One Microsoft Way",
"city": "Redmond",
"state": "WA",
"zip": "98052",
"country": "U.S."
}
],
"orderItems": [
{"id": 20240011, "productId": "123456", "productName": ".NET T-Shirt",
"unitPrice": 25, "units": 2, "discount": 0},
{"id": 20240012, "productId": "123457", "productName": ".NET Mug",
"unitPrice": 15, "units": 1, "discount": 0}
]
}
Introdução ao Azure Cosmos DB e à API nativa do Cosmos DB
O Azure Cosmos DB é o serviço de banco de dados distribuído globalmente da Microsoft para aplicativos de missão crítica. O Azure Cosmos DB proporciona distribuição global chave na mão, dimensionamento elástico de débito e armazenamento em todo o mundo, latências de milissegundos de um só dígito no percentil 99, cinco níveis de consistência bem definidos e elevada disponibilidade garantida, tudo com o suporte de SLAs líderes da indústria. O Azure Cosmos DB indexa automaticamente os dados sem que tenha de lidar com a gestão de esquemas e índices. É multimodal e suporte modelos de dados em documentos, chaves-valores, gráficos e em colunas.
Figura 7-19. Distribuição global do Azure Cosmos DB
Quando você usa um modelo C# para implementar a agregação a ser usada pela API do Azure Cosmos DB, a agregação pode ser semelhante às classes POCO C# usadas com o EF Core. A diferença está na maneira de usá-los a partir das camadas de aplicativo e infraestrutura, como no código a seguir:
// C# EXAMPLE OF AN ORDER AGGREGATE BEING PERSISTED WITH AZURE COSMOS DB API
// *** Domain Model Code ***
// Aggregate: Create an Order object with its child entities and/or value objects.
// Then, use AggregateRoot's methods to add the nested objects so invariants and
// logic is consistent across the nested properties (value objects and entities).
Order orderAggregate = new Order
{
Id = "2024001",
OrderDate = new DateTime(2005, 7, 1),
BuyerId = "1234567",
PurchaseOrderNumber = "PO18009186470"
}
Address address = new Address
{
Street = "100 One Microsoft Way",
City = "Redmond",
State = "WA",
Zip = "98052",
Country = "U.S."
}
orderAggregate.UpdateAddress(address);
OrderItem orderItem1 = new OrderItem
{
Id = 20240011,
ProductId = "123456",
ProductName = ".NET T-Shirt",
UnitPrice = 25,
Units = 2,
Discount = 0;
};
//Using methods with domain logic within the entity. No anemic-domain model
orderAggregate.AddOrderItem(orderItem1);
// *** End of Domain Model Code ***
// *** Infrastructure Code using Cosmos DB Client API ***
Uri collectionUri = UriFactory.CreateDocumentCollectionUri(databaseName,
collectionName);
await client.CreateDocumentAsync(collectionUri, orderAggregate);
// As your app evolves, let's say your object has a new schema. You can insert
// OrderV2 objects without any changes to the database tier.
Order2 newOrder = GetOrderV2Sample("IdForSalesOrder2");
await client.CreateDocumentAsync(collectionUri, newOrder);
Você pode ver que a maneira como você trabalha com seu modelo de domínio pode ser semelhante à maneira como você o usa na camada de modelo de domínio quando a infraestrutura é EF. Você ainda usa os mesmos métodos raiz agregados para garantir consistência, invariantes e validações dentro da agregação.
No entanto, quando você persiste seu modelo no banco de dados NoSQL, o código e a API mudam drasticamente em comparação com o código EF Core ou qualquer outro código relacionado a bancos de dados relacionais.
Implementar código .NET direcionado ao MongoDB e ao Azure Cosmos DB
Usar o Azure Cosmos DB a partir de contêineres .NET
Você pode acessar bancos de dados do Azure Cosmos DB a partir do código .NET em execução em contêineres, como de qualquer outro aplicativo .NET. Por exemplo, os microsserviços Locations.API e Marketing.API no eShopOnContainers são implementados para que possam consumir bancos de dados do Azure Cosmos DB.
No entanto, há uma limitação no Azure Cosmos DB do ponto de vista do ambiente de desenvolvimento do Docker. Embora haja um emulador local do Azure Cosmos DB que pode ser executado em uma máquina de desenvolvimento local, ele só oferece suporte ao Windows. Linux e macOS não são suportados.
Há também a possibilidade de executar este emulador no Docker, mas apenas em contêineres do Windows, não com contêineres do Linux. Essa é uma desvantagem inicial para o ambiente de desenvolvimento se seu aplicativo for implantado como contêineres Linux, já que, atualmente, você não pode implantar contêineres Linux e Windows no Docker para Windows ao mesmo tempo. Todos os contêineres que estão sendo implantados devem ser para Linux ou Windows.
A implantação ideal e mais direta para uma solução de desenvolvimento/teste é poder implantar seus sistemas de banco de dados como contêineres junto com seus contêineres personalizados para que seus ambientes de desenvolvimento/teste sejam sempre consistentes.
Use a API do MongoDB para contêineres locais de desenvolvimento/teste Linux/Windows e Azure Cosmos DB
Os bancos de dados do Cosmos DB suportam a API do MongoDB para .NET, bem como o protocolo de conexão nativo do MongoDB. Isso significa que, usando drivers existentes, seu aplicativo escrito para o MongoDB agora pode se comunicar com o Cosmos DB e usar bancos de dados do Cosmos DB em vez de bancos de dados do MongoDB, como mostra a Figura 7-20.
Figura 7-20. Usando a API e o protocolo do MongoDB para acessar o Azure Cosmos DB
Esta é uma abordagem muito conveniente para a prova de conceitos em ambientes Docker com contêineres Linux porque a imagem do Docker MongoDB é uma imagem multi-arch que suporta contêineres Docker Linux e contêineres Docker Windows.
Conforme mostrado na imagem a seguir, usando a API do MongoDB, o eShopOnContainers oferece suporte a contêineres do MongoDB Linux e Windows para o ambiente de desenvolvimento local, mas, em seguida, você pode mover para uma solução de nuvem PaaS escalável como Azure Cosmos DB simplesmente alterando a cadeia de conexão do MongoDB para apontar para o Azure Cosmos DB.
Figura 7-21. eShopOnContainers usando contêineres MongoDB para dev-env ou Azure Cosmos DB para produção
O Azure Cosmos DB de produção seria executado na nuvem do Azure como um serviço PaaS e escalável.
Seus contêineres .NET personalizados podem ser executados em um host Docker de desenvolvimento local (que está usando o Docker para Windows em uma máquina Windows 10) ou ser implantados em um ambiente de produção, como o Kubernetes no Azure AKS ou o Azure Service Fabric. Neste segundo ambiente, você implantaria apenas os contêineres personalizados do .NET, mas não o contêiner do MongoDB, já que usaria o Azure Cosmos DB na nuvem para lidar com os dados em produção.
Um benefício claro de usar a API do MongoDB é que sua solução pode ser executada em ambos os mecanismos de banco de dados, MongoDB ou Azure Cosmos DB, portanto, as migrações para ambientes diferentes devem ser fáceis. No entanto, às vezes vale a pena usar uma API nativa (que é a API nativa do Cosmos DB) para aproveitar ao máximo os recursos de um mecanismo de banco de dados específico.
Para obter mais comparação entre simplesmente usar o MongoDB versus o Cosmos DB na nuvem, consulte os Benefícios de usar o Azure Cosmos DB nesta página.
Analise sua abordagem para aplicativos de produção: API MongoDB vs. API Cosmos DB
No eShopOnContainers, estamos usando a API do MongoDB porque nossa prioridade era fundamentalmente ter um ambiente de desenvolvimento/teste consistente usando um banco de dados NoSQL que também pudesse funcionar com o Azure Cosmos DB.
No entanto, se você estiver planejando usar a API do MongoDB para acessar o Azure Cosmos DB no Azure para aplicativos de produção, deverá analisar as diferenças nos recursos e no desempenho ao usar a API do MongoDB para acessar bancos de dados do Azure Cosmos DB em comparação com o uso da API nativa do Azure Cosmos DB. Se for semelhante, você pode usar a API do MongoDB e obter o benefício de suportar dois mecanismos de banco de dados NoSQL ao mesmo tempo.
Você também pode usar clusters MongoDB como o banco de dados de produção na nuvem do Azure, também, com o MongoDB Azure Service. Mas esse não é um serviço de PaaS fornecido pela Microsoft. Nesse caso, o Azure está apenas hospedando essa solução vinda do MongoDB.
Basicamente, isso é apenas um aviso informando que você nem sempre deve usar a API do MongoDB contra o Azure Cosmos DB, como fizemos no eShopOnContainers porque era uma escolha conveniente para contêineres Linux. A decisão deve ser baseada nas necessidades específicas e testes que você precisa fazer para sua aplicação de produção.
O código: Use a API do MongoDB em aplicativos .NET
A API do MongoDB para .NET é baseada em pacotes NuGet que você precisa adicionar aos seus projetos, como no projeto Locations.API mostrado na figura a seguir.
Figura 7-22. Referências de pacotes NuGet da API do MongoDB em um projeto .NET
Vamos investigar o código nas seções a seguir.
Um modelo usado pela API do MongoDB
Primeiro, você precisa definir um modelo que armazenará os dados provenientes do banco de dados no espaço de memória do seu aplicativo. Aqui está um exemplo do modelo usado para Localizações em eShopOnContainers.
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver.GeoJsonObjectModel;
using System.Collections.Generic;
public class Locations
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public int LocationId { get; set; }
public string Code { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
public string Parent_Id { get; set; }
public string Description { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
public GeoJsonPoint<GeoJson2DGeographicCoordinates> Location
{ get; private set; }
public GeoJsonPolygon<GeoJson2DGeographicCoordinates> Polygon
{ get; private set; }
public void SetLocation(double lon, double lat) => SetPosition(lon, lat);
public void SetArea(List<GeoJson2DGeographicCoordinates> coordinatesList)
=> SetPolygon(coordinatesList);
private void SetPosition(double lon, double lat)
{
Latitude = lat;
Longitude = lon;
Location = new GeoJsonPoint<GeoJson2DGeographicCoordinates>(
new GeoJson2DGeographicCoordinates(lon, lat));
}
private void SetPolygon(List<GeoJson2DGeographicCoordinates> coordinatesList)
{
Polygon = new GeoJsonPolygon<GeoJson2DGeographicCoordinates>(
new GeoJsonPolygonCoordinates<GeoJson2DGeographicCoordinates>(
new GeoJsonLinearRingCoordinates<GeoJson2DGeographicCoordinates>(
coordinatesList)));
}
}
Você pode ver que há alguns atributos e tipos provenientes dos pacotes NuGet do MongoDB.
Os bancos de dados NoSQL geralmente são muito adequados para trabalhar com dados hierárquicos não relacionais. Neste exemplo, estamos usando tipos MongoDB especialmente feitos para geolocalizações, como GeoJson2DGeographicCoordinates
.
Recuperar o banco de dados e a coleção
No eShopOnContainers, criamos um contexto de banco de dados personalizado onde implementamos o código para recuperar o banco de dados e o MongoCollections, como no código a seguir.
public class LocationsContext
{
private readonly IMongoDatabase _database = null;
public LocationsContext(IOptions<LocationSettings> settings)
{
var client = new MongoClient(settings.Value.ConnectionString);
if (client != null)
_database = client.GetDatabase(settings.Value.Database);
}
public IMongoCollection<Locations> Locations
{
get
{
return _database.GetCollection<Locations>("Locations");
}
}
}
Recuperar os dados
No código C#, como controladores de API da Web ou implementação de repositórios personalizados, você pode escrever código semelhante ao seguinte ao consultar a API do MongoDB. Observe que o _context
objeto é uma instância da classe anterior LocationsContext
.
public async Task<Locations> GetAsync(int locationId)
{
var filter = Builders<Locations>.Filter.Eq("LocationId", locationId);
return await _context.Locations
.Find(filter)
.FirstOrDefaultAsync();
}
Use um env-var no arquivo docker-compose.override.yml para a cadeia de conexão MongoDB
Ao criar um objeto MongoClient, ele precisa de um parâmetro fundamental que é precisamente o ConnectionString
parâmetro que aponta para o banco de dados correto. No caso de eShopOnContainers, a cadeia de conexão pode apontar para um contêiner local do MongoDB Docker ou para um banco de dados do Azure Cosmos DB de "produção". Essa cadeia de conexão vem das variáveis de ambiente definidas nos arquivos usados ao docker-compose.override.yml
implantar com docker-compose ou Visual Studio, como no código yml a seguir.
# docker-compose.override.yml
version: '3.4'
services:
# Other services
locations-api:
environment:
# Other settings
- ConnectionString=${ESHOP_AZURE_COSMOSDB:-mongodb://nosqldata}
Importante
A Microsoft recomenda que você use o fluxo de autenticação mais seguro disponível. Se você estiver se conectando ao SQL do Azure, as Identidades Gerenciadas para recursos do Azure serão o método de autenticação recomendado.
A ConnectionString
variável de ambiente é resolvida desta forma: Se a ESHOP_AZURE_COSMOSDB
variável global for definida no .env
arquivo com a cadeia de conexão do Azure Cosmos DB, ela será usada para acessar o banco de dados do Azure Cosmos DB na nuvem. Se não estiver definido, ele pegará o mongodb://nosqldata
valor e usará o contêiner MongoDB de desenvolvimento.
O código a seguir mostra o .env
arquivo com a variável de ambiente global da cadeia de conexão do Azure Cosmos DB, conforme implementado no eShopOnContainers:
# .env file, in eShopOnContainers root folder
# Other Docker environment variables
ESHOP_EXTERNAL_DNS_NAME_OR_IP=host.docker.internal
ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP=<YourDockerHostIP>
#ESHOP_AZURE_COSMOSDB=<YourAzureCosmosDBConnData>
#Other environment variables for additional Azure infrastructure assets
#ESHOP_AZURE_REDIS_BASKET_DB=<YourAzureRedisBasketInfo>
#ESHOP_AZURE_STORAGE_CATALOG_URL=<YourAzureStorage_Catalog_BLOB_URL>
#ESHOP_AZURE_SERVICE_BUS=<YourAzureServiceBusInfo>
Descomente a linha ESHOP_AZURE_COSMOSDB e atualize-a com sua cadeia de conexão do Azure Cosmos DB obtida do portal do Azure, conforme explicado em Conectar um aplicativo MongoDB ao Azure Cosmos DB.
Se a ESHOP_AZURE_COSMOSDB
variável global estiver vazia, o que significa que ela é comentada .env
no arquivo, o contêiner usará uma cadeia de conexão MongoDB padrão. Essa cadeia de conexão aponta para o contêiner MongoDB local implantado no eShopOnContainers que é nomeado nosqldata
e foi definido no arquivo docker-compose, conforme mostrado no código .yml a seguir:
# docker-compose.yml
version: '3.4'
services:
# ...Other services...
nosqldata:
image: mongo
Recursos adicionais
Modelando dados de documentos para bancos de dados NoSQL
https://learn.microsoft.com/azure/cosmos-db/modeling-dataVaughn Vernon. A loja ideal de agregados de design orientado por domínio?
https://kalele.io/blog-posts/the-ideal-domain-driven-design-aggregate-store/Introdução ao Azure Cosmos DB: API para MongoDB
https://learn.microsoft.com/azure/cosmos-db/mongodb-introductionAzure Cosmos DB: Criar uma aplicação Web da MongoDB API com .NET e o portal do Azure
https://learn.microsoft.com/azure/cosmos-db/create-mongodb-dotnetUsar o emulador do Azure Cosmos DB para desenvolvimento e teste locais
https://learn.microsoft.com/azure/cosmos-db/local-emulatorLigar uma aplicação MongoDB ao Azure Cosmos DB
https://learn.microsoft.com/azure/cosmos-db/connect-mongodb-accountA imagem do Docker MongoDB (Linux e Windows Container)
https://hub.docker.com/_/mongo/Usar o MongoChef (Studio 3T) com uma conta do Azure Cosmos DB: API para MongoDB
https://learn.microsoft.com/azure/cosmos-db/mongodb-mongochef