Implementing the Ordering Service with the CQRS Pattern

CQRS is short for Command Query Responsibility Segregation. It is a pattern that separates read operations from update operations: queries return results without changing system state and have no side effects, while commands change system state. Idempotency means that executing the same command multiple times produces the same result with no side effects. Why Use the CQRS Pattern In traditional architectures, the same data model is used to query and update the database. This is simple and works well for basic CRUD operations. ...

March 10, 2026

Implementing the Permission Access Control List

Three Elements of Authorization Subject, resource, and operation. For example: a user (subject) reading and writing (operations) a file (resource). The relationship among subject, resource, and operation is called an authorization relation. Based on this relationship, we can abstract it into a triple (subject, resource, operation), and this triple is the basic unit of authorization. Role-Based Access Control Role-Based Access Control (RBAC) is an access control model that manages users’ access to resources through roles. A role is a collection of permissions, and users acquire the corresponding permissions by being assigned roles. The role-based access control model is simple and easy to use. There are other access control models as well, such as Attribute-Based Access Control (ABAC). ...

March 10, 2026

Implementing the Product Management Microservice

Creating Entities HelloWorld.ProductService.Entities.Products Product & CatalogBrand Using PostgreSQL with EF Core dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL Creating the DbContext namespace HelloWorld.ProductService.EntityFrameworks { public class ProductServiceDbContext(DbContextOptions<ProductServiceDbContext> options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); } } } Creating Entity Configuration Classes namespace HelloWorld.ProductService.EntityFrameworks.EntityConfigurations.Products { public class ProductEntityTypeConfiguration : IEntityTypeConfiguration<Product> { public void Configure(EntityTypeBuilder<Product> builder) { builder.ToTable("Products"); builder.Property(x => x.Name).HasMaxLength(32); builder.HasOne(x => x.Brand).WithMany(); } } } Database Connection String { "ConnectionStrings": { "ProductDatabase": "Host=localhost;Port=5432;Database=ProductService;Username=postgres;Password=postgres" } } Registering the DbContext builder.Services.AddDbContext<ProductServiceDbContext>(options => { options.UseNpgsql(builder.Configuration.GetConnectionString(DbConstants.ConnectionStringName)); }); Migrating the Database dotnet tool install --global dotnet-ef dotnet add package Microsoft.EntityFrameworkCore.Design dotnet ef migrations add InitialCreate --output-dir EntityFrameworks/Migrations dotnet ef database update Creating Models namespace HelloWorld.ProductService.Models.Products { public class ProductCreateRequest { public required string Name { get; init; } public string? Description { get; init; } public decimal Price { get; init; } public int BrandId { get; init; } public string? ImageUrl { get; init; } } } Creating Automatic Mappings namespace HelloWorld.ProductService.AutoMapper { public class ProductsMapConfiguration : Profile { public ProductsMapConfiguration() { CreateMap<ProductCreateRequest, Product>(); CreateMap<ProductUpdateRequest, Product>(); CreateMap<Product, ProductListItem>().AfterMap((src, dest) => dest.BrandName = src.Brand.Name); CreateMap<Product, ProductDetailsResponse>(); CreateMap<BrandCreateRequest, Brand>(); CreateMap<BrandUpdateRequest, Brand>(); CreateMap<Brand, BrandDetailsResponse>(); CreateMap<Brand, BrandListItem>(); } } } Creating Validators namespace HelloWorld.ProductService.Validations.Products { public class BrandUpdateRequestValidator : AbstractValidator<BrandUpdateRequest> { public BrandUpdateRequestValidator() { RuleFor(x => x.Id).GreaterThan(0); RuleFor(x => x.Name).NotNull().NotEmpty().Length(8, 32); } } } Creating Controllers namespace HelloWorld.ProductService.Controllers { [Route("api/[controller]")] [ApiController] public class ProductsController : ControllerBase } Defining Permissions namespace HelloWorld.ProductService.PermissionProviders { public static class CatalogPermissions public class CatalogPermissionDefiitionProvider : IPermissionDefinitionProvider } Localizing Permissions CatalogPermissionDefinitionProvider.en-US.resx CatalogPermissionDefinitionProvider.zh-CN.resx Localizing Entity Properties namespace HelloWorld.ProductService.Entities.Products { public class Product { [Display(Name = "ProductName")] public string Name { get; set; } } } Repetitive Work Repetitive work can be reduced with code generation tools, such as Visual Studio plugins, T4 templates, Roslyn, and so on.

March 10, 2026

Implementing the Query Pattern in CQRS

CQRS stands for Command Query Responsibility Segregation. In the CQRS pattern, commands and queries are implemented separately: commands handle write operations while queries handle read operations. This better fulfills the single responsibility principle and also enables better performance optimization. https://learn.microsoft.com/zh-cn/azure/architecture/patterns/cqrs https://www.xcode.me/Training/UnitNote/576 Defining the Order Query Interface public interface IOrderQueries { Task<OrderDetails> GetOrderAsync(int id); } Implementing the Order Query Interface public class OrderQueries : IOrderQueries { private readonly OrderingContext _context; public OrderQueries(OrderingContext context) { _context = context; dbContext.Database.SetConnectionString("db2_connection_string"); } public async Task<OrderDetails> GetOrderAsync(int id) { // do something } } Registering the Order Query Interface services.AddScoped<IOrderQueries, OrderQueries>(); Using the Order Query Interface public class OrderController : ControllerBase { private readonly IOrderQueries _orderQueries; public OrderController(IOrderQueries orderQueries) { _orderQueries = orderQueries; } [HttpGet("{id}")] public async Task<IActionResult> GetOrderAsync(int id) { var order = await _orderQueries.GetOrderAsync(id); return Ok(order); } }

March 10, 2026

Improving System Performance with Caching

Caching is a common technique aimed at improving the performance and scalability of a system. To cache data, frequently accessed data is temporarily copied into fast storage located close to the application. If this fast data store is closer to the application than the original source, caching can dramatically improve the response time of client applications by serving data more quickly. Cache Types Private In-Memory Cache A private cache means the caching service runs on the same server as the application; it is part of the application, usually a library or module. A private cache is typically an in-memory cache, such as MemoryCache or ConcurrentDictionary. It is a lightweight cache suitable for small applications. The advantage of a private cache is that it is simple and easy to use; the drawback is that data cannot be shared across servers, making it unsuitable for large applications. The IMemoryCache interface can be used to work with a private cache in .NET. ...

March 10, 2026

Injecting Time with the TimeProvider Class

System.TimeProvider is an abstraction of time that provides a point in time as a DateTimeOffset value. By using TimeProvider, you can make sure your code is testable and predictable. TimeProvider was introduced in .NET 8. Default Implementation By default, TimeProvider uses DateTimeOffset.UtcNow as its time source. Console.WriteLine($"Local: {TimeProvider.System.GetLocalNow()}"); Console.WriteLine($"Utc: {TimeProvider.System.GetUtcNow()}"); Custom Implementation public class CustomTimeProvider: TimeProvider { public override DateTimeOffset GetUtcNow() => DateTimeOffset.UtcNow.AddHours(1); } builder.Services.AddSingleton(TimeProvider.System); builder.Services.AddSingleton<TimeProvider, CustomTimeProvider>(); public class MyService { private readonly TimeProvider _timeProvider; public MyService(TimeProvider timeProvider) { _timeProvider = timeProvider; } public void DoSomething() { var now = _timeProvider.Now; Console.WriteLine(now); } } FakeTimeProvider Implementation dotnet add package Microsoft.Extensions.TimeProvider.Testing FakeTimeProvider fakeTimeProvider = new(); fakeTimeProvider.SetUtcNow(fakeTimeProvider.GetUtcNow().AddHours(1)); MyService service = new (timeProvider); service.DoSomething();

March 10, 2026

Integrating PostgreSQL database in Aspire

Add the PostgreSQL database to the Host project dotnet add package Aspire.Hosting.PostgreSQL var builder = DistributedApplication.CreateBuilder(args); var postgres = builder.AddPostgres("postgres"); var postgresdb = postgres.AddDatabase("postgresdb"); var exampleProject = builder.AddProject<Projects.ExampleProject>().WithReference(postgresdb); Add PostgreSQL pgAdmin resource var postgres = builder.AddPostgres("postgres").WithPgAdmin(); Add PostgreSQL pgWeb resource var postgres = builder.AddPostgres("postgres").WithPgWeb(); Reference the PostgreSQL database in the final application dotnet add package Aspire.Npgsql.EntityFrameworkCore.PostgreSQL builder.AddNpgsqlDbContext<IdentityServiceDbContext>(connectionName: DbConstants.ConnectionStringName, configureDbContextOptions: options => { new NpgsqlDbContextOptionsBuilder(options).MigrationsHistoryTable(DbConstants.MigrationsHistoryTableName); });

March 10, 2026

Load Testing and Stress Testing

Load testing Test whether the app can handle a specified user load under specific conditions while still meeting the response goals. The app runs in a normal state. Stress testing Test the app’s stability when running under extreme conditions, usually for a long period. The test places high user load on the app (spike or gradually increasing load) or restricts the app’s compute resources. Stress testing determines whether the app under stress can recover from failure and correctly return to the expected behavior. Under stress, the app runs under unusually high pressure. ...

March 10, 2026

Managing Databases with Azure Data Studio

Azure Data Studio is a lightweight, cross-platform database tool with a modern user interface that helps users manage databases more easily. Fully Open Source Azure Data Studio is an open source project with all of its code hosted on GitHub. Users are free to view the source code and can also contribute to the project. https://github.com/Microsoft/azuredatastudio Manageable Databases Azure Data Studio supports a variety of databases, including SQL Server, PostgreSQL, MySQL, TimescaleDB, Oracle, and more. By installing the corresponding extensions, it can support even more databases. ...

March 10, 2026

Model Binding

Model binding best practices An entity object is an EF concept; each entity object maps to a table in the database. A model object is an MVC concept; it is the data structure of HTTP requests and responses. HTTP requests carry data through URL parameters, forms, headers, JSON payloads, and so on. This data is ultimately bound into model objects, and after the model is converted to an entity object, it is persisted to the database. ...

March 10, 2026