Udostępnij za pośrednictwem


Używanie baz danych NoSQL jako infrastruktury trwałości

Napiwek

Ta zawartość jest fragmentem książki eBook, architektury mikrousług platformy .NET dla konteneryzowanych aplikacji platformy .NET dostępnych na platformie .NET Docs lub jako bezpłatnego pliku PDF, który można odczytać w trybie offline.

Architektura mikrousług platformy .NET dla konteneryzowanych aplikacji platformy .NET — miniatura książki eBook.

W przypadku korzystania z baz danych NoSQL dla warstwy danych infrastruktury zwykle nie używa się orm, takiego jak Entity Framework Core. Zamiast tego używasz interfejsu API udostępnianego przez aparat NoSQL, takiego jak Azure Cosmos DB, MongoDB, Cassandra, RavenDB, CouchDB lub Tabele usługi Azure Storage.

Jednak w przypadku korzystania z bazy danych NoSQL, zwłaszcza bazy danych zorientowanej na dokumenty, takiej jak Azure Cosmos DB, CouchDB lub RavenDB, sposób projektowania modelu za pomocą agregacji DDD jest częściowo podobny do sposobu, w jaki można to zrobić w programie EF Core, w odniesieniu do identyfikacji agregowanych korzeni, klas jednostek podrzędnych i klas obiektów wartości. Jednak ostatecznie wybór bazy danych będzie mieć wpływ na projekt.

W przypadku korzystania z bazy danych zorientowanej na dokument implementujesz agregację jako pojedynczy dokument, serializowany w formacie JSON lub innym formacie. Jednak użycie bazy danych jest niewidoczne z punktu widzenia kodu modelu domeny. W przypadku korzystania z bazy danych NoSQL nadal używasz klas jednostek i agregacji klas głównych, ale z większą elastycznością niż w przypadku korzystania z programu EF Core, ponieważ trwałość nie jest relacyjna.

Różnica polega na utrwalaniu tego modelu. Jeśli model domeny został zaimplementowany na podstawie klas jednostek POCO, niezależny od trwałości infrastruktury, może to wyglądać tak, jakby można było przejść do innej infrastruktury trwałości, nawet z relacyjnej do NoSQL. Jednak nie powinno to być twoim celem. Istnieją zawsze ograniczenia i kompromisy w różnych technologiach baz danych, więc nie będzie można mieć tego samego modelu dla relacyjnych lub baz danych NoSQL. Zmiana modeli trwałości nie jest prostym zadaniem, ponieważ transakcje i operacje trwałości będą bardzo różne.

Na przykład w bazie danych zorientowanej na dokument jest w porządku, aby zagregowany katalog główny miał wiele właściwości kolekcji podrzędnej. W relacyjnej bazie danych wykonywanie zapytań o wiele właściwości kolekcji podrzędnej nie jest łatwo zoptymalizowane, ponieważ otrzymujesz instrukcję UNION ALL SQL z powrotem z platformy EF. Posiadanie tego samego modelu domeny dla relacyjnych baz danych lub baz danych NoSQL nie jest proste i nie należy tego robić. Naprawdę musisz zaprojektować model ze zrozumieniem, w jaki sposób dane będą używane w każdej konkretnej bazie danych.

Zaletą korzystania z baz danych NoSQL jest to, że jednostki są bardziej zdenormalizowane, więc nie ustawiasz mapowania tabeli. Model domeny może być bardziej elastyczny niż w przypadku korzystania z relacyjnej bazy danych.

Podczas projektowania modelu domeny na podstawie agregacji przenoszenie do baz danych NoSQL i baz danych zorientowanych na dokumenty może być jeszcze łatwiejsze niż użycie relacyjnej bazy danych, ponieważ agregacje projektowane są podobne do serializowanych dokumentów w bazie danych zorientowanej na dokumenty. Następnie możesz dołączyć do tych "worków" wszystkie informacje, które mogą być potrzebne do tego agregacji.

Na przykład poniższy kod JSON to przykładowa implementacja agregacji zamówień podczas korzystania z bazy danych zorientowanej na dokument. Jest ona podobna do agregacji zamówień zaimplementowanych w przykładzie eShopOnContainers, ale bez użycia platformy EF Core poniżej.

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

Wprowadzenie do usługi Azure Cosmos DB i natywnego interfejsu API usługi Cosmos DB

Azure Cosmos DB to globalnie rozproszona usługa bazy danych firmy Microsoft dla aplikacji o krytycznym znaczeniu. Usługa Azure Cosmos DB zapewnia kompleksową globalną dystrybucję, elastyczne skalowanie przepływności i pamięci w skali globalnej, milisekundowe opóźnienia w 99. percentylu, pięć odpowiednio zdefiniowanych poziomów spójności oraz gwarantowaną wysoką dostępność, wspierane przez wiodące w branży umowy SLA. Usługa Azure Cosmos DB automatycznie indeksuje dane bez konieczności zarządzania schematami i indeksami. To usługa wielomodelowa, obsługująca modele oparte na dokumentach, parach klucz-wartość, grafach i kolumnach.

Diagram przedstawiający dystrybucję globalną usługi Azure Cosmos DB.

Rysunek 7–19. Globalna dystrybucja usługi Azure Cosmos DB

Jeśli używasz modelu języka C# do implementowania agregacji używanej przez interfejs API usługi Azure Cosmos DB, agregacja może być podobna do klas POCO języka C# używanych z platformą EF Core. Różnica polega na używaniu ich z warstw aplikacji i infrastruktury, jak w poniższym kodzie:

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

Widać, że sposób pracy z modelem domeny może być podobny do sposobu używania go w warstwie modelu domeny, gdy infrastruktura jest ef. Nadal używasz tych samych zagregowanych metod głównych, aby zapewnić spójność, niezmienności i walidacje w ramach agregacji.

Jednak w przypadku utrwalania modelu w bazie danych NoSQL kod i interfejs API zmieniają się znacznie w porównaniu z kodem EF Core lub innym kodem powiązanym z relacyjnymi bazami danych.

Implementowanie kodu platformy .NET przeznaczonego dla bazy danych MongoDB i usługi Azure Cosmos DB

Korzystanie z usługi Azure Cosmos DB z kontenerów platformy .NET

Dostęp do baz danych usługi Azure Cosmos DB można uzyskać z poziomu kodu platformy .NET uruchomionego w kontenerach, na przykład z dowolnej innej aplikacji .NET. Na przykład mikrousługi Locations.API i Marketing.API w usłudze eShopOnContainers są implementowane, aby mogły korzystać z baz danych usługi Azure Cosmos DB.

Jednak z punktu widzenia środowiska deweloperskiego platformy Docker istnieje ograniczenie w usłudze Azure Cosmos DB. Mimo że istnieje lokalny emulator usługi Azure Cosmos DB, który może działać na lokalnej maszynie deweloperskiej, obsługuje tylko system Windows. Systemy Linux i macOS nie są obsługiwane.

Istnieje również możliwość uruchomienia tego emulatora na platformie Docker, ale tylko w kontenerach systemu Windows, a nie w kontenerach systemu Linux. Jest to początkowy handicap dla środowiska deweloperskiego, jeśli aplikacja jest wdrożona jako kontenery systemu Linux, ponieważ obecnie nie można wdrożyć kontenerów systemu Linux i Windows na platformie Docker dla systemu Windows w tym samym czasie. Wszystkie wdrożone kontenery muszą być przeznaczone dla systemu Linux lub Windows.

Idealnym i prostszym wdrożeniem rozwiązania deweloperskiego/testowego jest możliwość wdrożenia systemów baz danych jako kontenerów wraz z kontenerami niestandardowymi, dzięki czemu środowiska deweloperskie/testowe są zawsze spójne.

Używanie interfejsu API bazy danych MongoDB na potrzeby lokalnego tworzenia i testowania kontenerów systemu Linux/Windows oraz usługi Azure Cosmos DB

Bazy danych Usługi Cosmos DB obsługują interfejs API bazy danych MongoDB dla platformy .NET, a także natywny protokół przewodowy bazy danych MongoDB. Oznacza to, że przy użyciu istniejących sterowników aplikacja napisana dla bazy danych MongoDB może teraz komunikować się z usługą Cosmos DB i używać baz danych Cosmos DB zamiast baz danych MongoDB, jak pokazano na rysunku 7–20.

Diagram pokazujący, że usługa Cosmos DB obsługuje protokół przewodowy .NET i MongoDB.

Rysunek 7–20. Uzyskiwanie dostępu do usługi Azure Cosmos DB przy użyciu interfejsu API i protokołu bazy danych MongoDB

Jest to bardzo wygodne podejście do weryfikacji koncepcji w środowiskach platformy Docker z kontenerami systemu Linux, ponieważ obraz platformy Docker bazy danych MongoDB jest obrazem wielo arch obsługującym kontenery systemu Docker Linux i kontenery systemu Windows platformy Docker.

Jak pokazano na poniższej ilustracji, korzystając z interfejsu API bazy danych MongoDB, moduł eShopOnContainers obsługuje kontenery MongoDB Linux i Windows dla lokalnego środowiska deweloperskiego, ale następnie można przejść do skalowalnego rozwiązania w chmurze PaaS jako usługi Azure Cosmos DB, po prostu zmieniając parametry połączenia MongoDB, aby wskazać usługę Azure Cosmos DB.

Diagram przedstawiający mikrousługę Location w aplikacji eShopOnContainers może używać usługi Cosmos DB lub Mongo DB.

Rysunek 7–21. eShopOnContainers korzystające z kontenerów bazy danych MongoDB na potrzeby dev-env lub Azure Cosmos DB w środowisku produkcyjnym

Produkcyjna usługa Azure Cosmos DB będzie działać w chmurze platformy Azure jako usługa PaaS i skalowalna.

Niestandardowe kontenery platformy .NET można uruchamiać na lokalnym hoście platformy Docker (korzystającym z platformy Docker dla systemu Windows na maszynie z systemem Windows 10) lub być wdrażane w środowisku produkcyjnym, takim jak Kubernetes w usłudze Azure AKS lub Azure Service Fabric. W tym drugim środowisku wdrożysz tylko niestandardowe kontenery platformy .NET, ale nie kontener bazy danych MongoDB, ponieważ używasz usługi Azure Cosmos DB w chmurze do obsługi danych w środowisku produkcyjnym.

Wyraźną zaletą korzystania z interfejsu API bazy danych MongoDB jest to, że rozwiązanie może działać zarówno w aparatach bazy danych, mongoDB, jak i usłudze Azure Cosmos DB, więc migracje do różnych środowisk powinny być łatwe. Jednak czasami warto użyć natywnego interfejsu API (czyli natywnego interfejsu API usługi Cosmos DB), aby w pełni wykorzystać możliwości określonego aparatu bazy danych.

Aby uzyskać dalsze porównanie między używaniem bazy danych MongoDB a usługą Cosmos DB w chmurze, zobacz Zalety korzystania z usługi Azure Cosmos DB na tej stronie.

Analizowanie podejścia do aplikacji produkcyjnych: interfejs API bazy danych MongoDB a interfejs API usługi Cosmos DB

W usłudze eShopOnContainers używamy interfejsu API bazy danych MongoDB, ponieważ naszym priorytetem było posiadanie spójnego środowiska deweloperskiego/testowego przy użyciu bazy danych NoSQL, która może również współpracować z usługą Azure Cosmos DB.

Jeśli jednak planujesz używać interfejsu API bazy danych MongoDB do uzyskiwania dostępu do usługi Azure Cosmos DB na platformie Azure na potrzeby aplikacji produkcyjnych, należy przeanalizować różnice w możliwościach i wydajności podczas korzystania z interfejsu API bazy danych MongoDB w celu uzyskania dostępu do baz danych usługi Azure Cosmos DB w porównaniu z użyciem natywnego interfejsu API usługi Azure Cosmos DB. Jeśli jest podobny, możesz użyć interfejsu API bazy danych MongoDB i skorzystać z zalet obsługi dwóch aparatów baz danych NoSQL w tym samym czasie.

Klastry Bazy danych MongoDB można również używać jako produkcyjnej bazy danych w chmurze platformy Azure za pomocą usługi Platformy Azure MongoDB. Nie jest to jednak usługa PaaS zapewniana przez firmę Microsoft. W takim przypadku platforma Azure obsługuje tylko to rozwiązanie pochodzące z bazy danych MongoDB.

Zasadniczo jest to tylko zastrzeżenie stwierdzające, że nie zawsze należy używać interfejsu API bazy danych MongoDB w usłudze Azure Cosmos DB, podobnie jak w przypadku modułów eShopOnContainers, ponieważ był to wygodny wybór dla kontenerów systemu Linux. Decyzja powinna być oparta na konkretnych potrzebach i testach, które należy wykonać dla aplikacji produkcyjnej.

Kod: Korzystanie z interfejsu API bazy danych MongoDB w aplikacjach platformy .NET

Interfejs API bazy danych MongoDB dla platformy .NET jest oparty na pakietach NuGet, które należy dodać do projektów, na przykład w projekcie Locations.API pokazanym na poniższej ilustracji.

Zrzut ekranu przedstawiający zależności w pakietach NuGet bazy danych MongoDB.

Rysunek 7–22. Odwołania do pakietów NuGet interfejsu API bazy danych MongoDB w projekcie platformy .NET

Zbadajmy kod w poniższych sekcjach.

Model używany przez interfejs API bazy danych MongoDB

Najpierw należy zdefiniować model, który będzie przechowywać dane pochodzące z bazy danych w przestrzeni pamięci aplikacji. Oto przykład modelu używanego dla lokalizacji w 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)));
    }
}

Widać, że istnieje kilka atrybutów i typów pochodzących z pakietów NuGet bazy danych MongoDB.

Bazy danych NoSQL są zwykle bardzo odpowiednie do pracy z danymi hierarchicznymi nierelacyjnymi. W tym przykładzie używamy typów bazy danych MongoDB, szczególnie dla lokalizacji geograficznych, takich jak GeoJson2DGeographicCoordinates.

Pobieranie bazy danych i kolekcji

W aplikacji eShopOnContainers utworzyliśmy niestandardowy kontekst bazy danych, w którym implementujemy kod w celu pobrania bazy danych i kolekcji MongoCollections, jak w poniższym kodzie.

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

Pobieranie danych

W kodzie języka C#, takich jak kontrolery interfejsu API sieci Web lub implementacja repozytoriów niestandardowych, możesz napisać podobny kod do poniższego podczas wykonywania zapytań za pomocą interfejsu API bazy danych MongoDB. Należy pamiętać, że _context obiekt jest wystąpieniem poprzedniej LocationsContext klasy.

public async Task<Locations> GetAsync(int locationId)
{
    var filter = Builders<Locations>.Filter.Eq("LocationId", locationId);
    return await _context.Locations
                            .Find(filter)
                            .FirstOrDefaultAsync();
}

Użyj env-var w pliku docker-compose.override.yml dla bazy danych MongoDB parametry połączenia

Podczas tworzenia obiektu MongoClient potrzebuje podstawowego parametru, który jest dokładnie ConnectionString parametrem wskazującym właściwą bazę danych. W przypadku aplikacji eShopOnContainers parametry połączenia może wskazywać lokalny kontener Platformy Docker bazy danych MongoDB lub "produkcyjną" bazę danych Usługi Azure Cosmos DB. Ta parametry połączenia pochodzi ze zmiennych środowiskowych zdefiniowanych w docker-compose.override.yml plikach używanych podczas wdrażania za pomocą narzędzia docker-compose lub Visual Studio, jak w poniższym kodzie yml.

# docker-compose.override.yml
version: '3.4'
services:
  # Other services
  locations-api:
    environment:
      # Other settings
      - ConnectionString=${ESHOP_AZURE_COSMOSDB:-mongodb://nosqldata}

Ważne

Firma Microsoft zaleca korzystanie z najbezpieczniejszego dostępnego przepływu uwierzytelniania. Jeśli łączysz się z usługą Azure SQL, tożsamości zarządzane dla zasobów platformy Azure to zalecana metoda uwierzytelniania.

Zmienna ConnectionString środowiskowa jest rozpoznawana w ten sposób: jeśli ESHOP_AZURE_COSMOSDB zmienna globalna jest zdefiniowana w pliku za .env pomocą parametry połączenia usługi Azure Cosmos DB, będzie ona używana do uzyskiwania dostępu do bazy danych usługi Azure Cosmos DB w chmurze. Jeśli ta wartość nie zostanie zdefiniowana, zostanie użyta mongodb://nosqldata wartość i użyje deweloperskiego kontenera MongoDB.

Poniższy kod przedstawia .env plik z parametry połączenia globalnej zmiennej środowiskowej usługi Azure Cosmos DB, zgodnie z implementacją w aplikacji 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>

Usuń komentarz z wiersza ESHOP_AZURE_COSMOSDB i zaktualizuj go przy użyciu parametry połączenia usługi Azure Cosmos DB uzyskanej z witryny Azure Portal, zgodnie z wyjaśnieniem w temacie Connect a MongoDB application to Azure Cosmos DB (Łączenie aplikacji Bazy danych MongoDB z usługą Azure Cosmos DB).

Jeśli zmienna ESHOP_AZURE_COSMOSDB globalna jest pusta, co oznacza, że jest oznaczona jako komentarz w .env pliku, kontener używa domyślnej parametry połączenia bazy danych MongoDB. Ten parametry połączenia wskazuje lokalny kontener MongoDB wdrożony w module eShopOnContainers o nazwie nosqldata i został zdefiniowany w pliku docker-compose, jak pokazano w poniższym kodzie .yml:

# docker-compose.yml
version: '3.4'
services:
  # ...Other services...
  nosqldata:
    image: mongo

Dodatkowe zasoby