practical-dotnet-aspire Documentation

repository·main·Indexed 19 days ago

https://github.com/thangchung/practical-dotnet-aspire

A demonstration project for building and deploying microservices using .NET 9 and .NET Aspire 9. It features a CoffeeShop domain implementing Vertical Slicing, Domain-Driven Design (DDD), CQRS with MediatR, and asynchronous messaging via MassTransit and RabbitMQ. The project showcases AI integration using Microsoft.Extensions.AI for semantic search and automated data seeding via Ollama and Azure OpenAI, alongside observability with OpenTelemetry.

Tokens
3.5K
Snippets
10
Records
14
Agent score
65%

What's inside practical-dotnet-aspire

  1. Overview of CoffeeShop App technology stack

    main

    The CoffeeShop application is a demonstration of microservices development using .NET Aspire. Key technologies and patterns include:

    • Runtime: .NET 9.0 STS and .NET Aspire 9.
    • Architecture: Microservices following Vertical Slicing principles and Domain-Driven Design (DDD) building blocks.
    • Patterns: CQRS implemented with MediatR and FluentValidation.
    • Observability: Built-in OpenTelemetry with custom instrumentation for MediatR, FluentValidation handlers, and MassTransit consumers.
    • Data & Mapping: Mapperly for object mapping and Npgsql.EntityFrameworkCore.PostgreSQL for database interactions (utilizing UUID v7).
    • Communication: Asynchronous messaging via MassTransit and RabbitMQ.
    • API: OpenAPI support and API Versioning.
    • AI Integration: Microsoft.Extensions.AI supporting Ollama (local development) and Azure OpenAI (production/cloud).
    • Testing: Integration testing using .NET Aspire and Wiremock.NET.
  2. Maintain message namespaces for MassTransit integration

    main
    When working with integration events in the counter-api, you must not change the namespace of the message classes. The MassTransit library relies on the exact namespace of these messages to correctly route and consume them. Changing the namespace will prevent MassTransit from being able to consume the messages, breaking the integration between services.
  3. AI Implementation: Seeding data and Semantic Searching

    main

    The CoffeeShop app demonstrates intelligent application development through two primary AI patterns:

    1. Seeding data via Chat Completion: Using LLMs to generate initial datasets for the application.
    2. Semantic Searching via Vector Embeddings: Implementing search capabilities based on the meaning of queries rather than exact keyword matches.

    These features are implemented using Microsoft.Extensions.AI and can be configured to use Ollama for local development or Azure OpenAI for cloud-based services.

  4. Run tests and generate coverage reports for counter-api-tests

    main

    To run the tests for the counter-api-tests project and generate a visual HTML coverage report, follow these steps:

    1. Install the ReportGenerator tool (if not already installed): Use the dotnet-reportgenerator-globaltool to convert coverage files into readable reports.
    2. Execute tests: Run dotnet test using the provided tests.runsettings file to ensure the correct test configuration is applied.
    3. Generate the report: Use reportgenerator to scan for .xml Cobertura coverage files in the TestResults directories and output an HTML report to a coverage folder.
    4. View the report: Open the generated index.htm in a web browser.
    # 1. Install prerequisite tool
    dotnet tool install -g dotnet-reportgenerator-globaltool
    
    # 2. Run tests with specific settings
    dotnet test --settings tests.runsettings
    
    # 3. Generate HTML coverage report
    reportgenerator `
    	-reports:".\**\TestResults\**\coverage.cobertura.xml" `
    	-targetdir:"coverage" `
    	-reporttypes:Html
    
    # 4. Open the report
    .\coverage\index.htm
  5. Get started with the CoffeeShop Apps on .NET Aspire

    main

    To run the CoffeeShop application locally, you can use Visual Studio or the .NET CLI. The application is built on .NET 9.0 and .NET Aspire 9, utilizing a microservices architecture.

    Using Visual Studio

    1. Open the project in Visual Studio.
    2. Press F5 to build and run the application.

    Using .NET CLI

    Run the following commands from the root directory:

    > dotnet build coffeeshop-aspire.sln
    > dotnet run --project app-host/CoffeeShop.AppHost.csproj

    Once running, the application dashboard/entry point is available at http://localhost:5019.

  6. Configure Docker Compose environment variables

    main

    When using the provided Docker Compose configuration, you can control the container images and registry using the following environment variables:

    • DOCKER_REGISTRY: The registry URL. Defaults to ghcr.io/thangchung/coffeeshop-aspire.
    • IMAGE_TAG: The specific image tag to pull. Defaults to latest.
    # Example usage
    export DOCKER_REGISTRY=my-private-registry.io
    export IMAGE_TAG=v1.0.0
    docker-compose up
  7. Place an order using PlaceOrderCommand

    main

    The PlaceOrderCommand is a MediatR request used to initiate an order within the CounterApi. It encapsulates all necessary details for an order, including the source, location, and specific items for both the Barista (drinks) and Kitchen (food).

    var command = new PlaceOrderCommand
    {
        OrderId = Guid.NewGuid(), // Or use GuidHelper.NewGuid()
        CommandType = CommandType.PLACE_ORDER,
        OrderSource = OrderSource.COUNTER,
        Location = Location.ATLANTA,
        LoyaltyMemberId = Guid.NewGuid(),
        BaristaItems = new List<CommandItem> 
        {
            new CommandItem { ItemType = ItemType.COFFEE } 
        },
        KitchenItems = new List<CommandItem> 
        {
            new CommandItem { ItemType = ItemType.PASTRY } 
        },
        Timestamp = DateTime.UtcNow
    };
    
    // Sent via MediatR
    var result = await _mediator.Send(command);
  8. Configure RabbitMQ service

    main

    The RabbitMQ service is used for messaging and exposes the following ports:

    • 5672: RabbitMQ broker port.
    • 15672: RabbitMQ Management UI port.

    It uses the masstransit/rabbitmq:latest image and includes a healthcheck using rabbitmq-diagnostics -q ping.

    rabbitmq:
        image: masstransit/rabbitmq:latest
        healthcheck:
          test: rabbitmq-diagnostics -q ping
          interval: 30s
          timeout: 30s
          retries: 3
        ports:
          - "5672:5672"
          - "15672:15672"
        networks:
          - coffeeshop-network
  9. Reference: PlaceOrderCommand properties

    main

    The following properties define the structure of a PlaceOrderCommand request:

    public class PlaceOrderCommand : IRequest<IResult>
    {
        public Guid OrderId { get; set; } // Defaults to GuidHelper.NewGuid()
        public CommandType CommandType { get; set; } // Defaults to CommandType.PLACE_ORDER
        public OrderSource OrderSource { get; set; } // Defaults to OrderSource.COUNTER
        public Location Location { get; set; } // Defaults to Location.ATLANTA
        public Guid LoyaltyMemberId { get; set; } // Defaults to GuidHelper.NewGuid()
        public List<CommandItem> BaristaItems { get; set; } // List of items for the barista
        public List<CommandItem> KitchenItems { get; set; } // List of items for the kitchen
        public DateTime Timestamp { get; set; } // Defaults to DateTimeHelper.NewDateTime()
    }
  10. Configure API Service Environment Variables

    main

    The application APIs (product-api, counter-api, barista-api, kitchen-api, and order-summary) use specific environment variables for OpenTelemetry (OTEL) observability and service discovery.

    Observability (OpenTelemetry)

    All APIs use these settings to enable detailed telemetry:

    • OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: Set to true to include exception details in logs.
    • OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: Set to true to include event details in logs.
    • OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: Set to in_memory for retry logic.

    Service Discovery and Connections

    Services connect to each other and infrastructure using the following patterns:

    • RabbitMQ Connection String: ConnectionStrings__rabbitmq uses the format amqp://[user]:[pass]@rabbitmq:5672.
    • PostgreSQL Connection String: ConnectionStrings__postgres uses the standard .NET format: Host=postgresQL;Port=5432;Username=postgres;Password=2lUmFKentK!fuyjdGIK4ka;Database=postgres.
    • Inter-service HTTP: Services reference each other via environment variables like services__product-api__http__0 (e.g., http://product-api:8080).
    • Forwarded Headers: ASPNETCORE_FORWARDEDHEADERS_ENABLED is set to true to handle proxying correctly.
    environment:
      OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true"
      OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true"
      OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory"
      ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
      ConnectionStrings__rabbitmq: "amqp://guest:m7YZc!8nqV6bmTb8VKM318@rabbitmq:5672"
      ConnectionStrings__postgres: "Host=postgresQL;Port=5432;Username=postgres;Password=2lUmFKentK!fuyjdGIK4ka;Database=postgres"
      services__product-api__http__0: "http://product-api:8080"