Use Marten Transactional Document DB for Catalog Microservice with .NET 8

Mehmet Ozkaya
5 min readMar 23, 2024

--

In this article, we’ll build a robust Catalog Microservice, we’re embracing the power of Marten to handle our data persistence.

Marten, with its seamless integration with PostgreSQL, allows us to treat our relational database as a Document DB, simplifying the CRUD operations for our Product entities.

I have just published course — .NET 8 Microservices: C# 12, DDD, CQRS, Vertical/Clean Architecture.

Here, we delve into how these services, particularly databases and message brokers, underpin our EShop microservices, ensuring robust, scalable operations.

📦 Marten: Unleashing PostgreSQL’s Document DB Powers

Our Catalog microservice’s dalliance with PostgreSQL is not your typical ORM story. Here’s where Marten steps in, wielding PostgreSQL’s JSON capabilities to offer us the best of both worlds: the agility of a NoSQL document database and the steadfast reliability of a relational system.

Why Marten for Catalog?

  • Document-Oriented Storage: With PostgreSQL’s JSON and JSONB data types, Marten allows us to model our domain in a more natural, flexible way, akin to a NoSQL document database.
  • Transactional Integrity: Marten ensures that even in our document-oriented model, we don’t compromise on the transactional guarantees PostgreSQL is renowned for.
  • Rich Query Capabilities: Leveraging PostgreSQL’s powerful indexing and querying features, Marten provides a rich set of querying capabilities right out of the box.

The Heartbeat of Marten: IDocumentStore

At the core lies the IDocumentStore, the epicenter from where all actions originate. But the true magic happens when we delve into the session types spawned from this store, each designed for specific scenarios.

📖 Query Sessions: The Read-Optimized Pathway

When your goal is purely to read documents without the overhead of tracking changes, the Query Session shines. It’s lean, focused, and perfect for scenarios where you’re consuming data without altering it.

using var querySession = documentStore.QuerySession();
// Perform read operations

📝 Document Sessions: The Versatile Workhorse

For the times when your operations are a mix of reads and writes, the Document Session steps in. It’s the versatile session that caters to a broader range of operations, from querying to updating documents.

Identity Map Session: The Smart Cache

The Identity Map pattern is a clever caching mechanism that Marten employs, ensuring that repeated requests for the same document within a session don’t hit the database unnecessarily. It’s a feature that shines in web requests or service bus scenarios.

using var documentSession = documentStore.OpenSession(); // Default is IdentityMap
// Load, modify, and persist documents

Lightweight Session: Quick and Efficient

When you’re dealing with transactions that are light on reads but heavy on writes, the Lightweight Session is your ally. It maintains the transactional integrity without the overhead of tracking document changes.

using var lightweightSession = documentStore.LightweightSession();
// Perform updates or inserts

Dirty Checking Session: The Vigilant Observer

For those critical moments when you need to know exactly what changed, the Dirty Checking Session comes into play. It meticulously compares the original and current states of your documents, ensuring that no alteration goes unnoticed.

using var dirtyCheckingSession = documentStore.DirtyTrackedSession();
// Load and modify documents; Marten keeps an eye on changes

In Action: Leveraging Marten Sessions

Each session type in Marten is a tool in your arsenal, crafted to suit specific needs. Whether you’re reading vast amounts of data, engaging in complex transactions, or need an eagle eye on document changes, Marten’s sessions provide a streamlined, efficient pathway to interact with your PostgreSQL-stored documents.

🚀 Launching into Development

With Marten configured, we’re set to develop our Catalog API with the agility of document-oriented models while standing on the solid ground of PostgreSQL’s robustness. It’s a compelling combination that promises to elevate our microservice to new heights of efficiency and flexibility.

Setting the Stage with Marten

Before diving into the code, let’s ensure Marten is part of our toolkit. Given its specific utility for the Catalog and potentially the Basket microservices, we’ll directly integrate Marten into our Catalog API project.

# Install Marten NuGet Package in Catalog.API Project
dotnet add package Marten

Injecting Marten’s IDocumentSession

With Marten installed, our next move is to inject the IDocumentSession directly into our command handlers. This direct injection streamlines our data operations, allowing us to interact with the database without additional layers of abstraction.

Crafting the CreateProductCommandHandler

Now, let’s focus on the CreateProductCommandHandler, where the essence of our CRUD operation resides. Here, we'll harness the injected IDocumentSession to store our Product entities.

using BuildingBlocks.CQRS;
using Catalog.API.Models;
using Marten;

namespace Catalog.API.Products.CreateProduct;
public record CreateProductCommand(string Name, List<string> Category, string Description, string ImageFile, decimal Price)
: ICommand<CreateProductResult>;
public record CreateProductResult(Guid Id);
internal class CreateProductCommandHandler : ICommandHandler<CreateProductCommand, CreateProductResult>
{
private readonly IDocumentSession _session;
private readonly ILogger<CreateProductCommandHandler> _logger;
public CreateProductCommandHandler(IDocumentSession session, ILogger<CreateProductCommandHandler> logger)
{
_session = session;
_logger = logger;
}
public async Task<CreateProductResult> Handle(CreateProductCommand command, CancellationToken cancellationToken)
{
_logger.LogInformation("Handling CreateProductCommand: {@Command}", command);
var product = new Product
{
Name = command.Name,
Category = command.Category,
Description = command.Description,
ImageFile = command.ImageFile,
Price = command.Price
};
_session.Store(product);
await _session.SaveChangesAsync(cancellationToken);
return new CreateProductResult(product.Id);
}
}

In this handler:

  • We construct a Product entity from the incoming command.
  • Utilize the _session.Store(product) method to add the product to the session.
  • Commit the transaction with _session.SaveChangesAsync(cancellationToken), persisting the product to the database.
  • Return a result containing the new product’s ID.

🚀Conclusion

Integrating Marten into our Catalog API’s CommandHandler for product persistence showcases Marten's capability to streamline database interactions within a microservices architecture. By directly injecting IDocumentSession and leveraging its transactional operations, we've crafted an efficient pathway for persisting product data, setting a solid foundation for our Catalog API's data layer.

I have just published course — .NET 8 Microservices: C# 12, DDD, CQRS, Vertical/Clean Architecture.

This is step-by-step development of reference microservices architecture that include microservices on .NET platforms which used ASP.NET Web API, Docker, RabbitMQ, MassTransit, Grpc, Yarp API Gateway, PostgreSQL, Redis, SQLite, SqlServer, Marten, Entity Framework Core, CQRS, MediatR, DDD, Vertical and Clean Architecture implementation with using latest features of .NET 8 and C# 12.

--

--

Mehmet Ozkaya
Mehmet Ozkaya

Written by Mehmet Ozkaya

Software Architect | Udemy Instructor | AWS Community Builder | Cloud-Native and Serverless Event-driven Microservices https://github.com/mehmetozkaya

No responses yet