(一)微服务(垂直AP/分布式缓存/装饰器Pattern)

发布于:2025-05-31 ⋅ 阅读:(21) ⋅ 点赞:(0)


项目地址

  • 教程作者:
  • 教程地址:
  • 代码仓库地址:
  • 所用到的框架和插件:
dbt 
airflow

一、创建第一个垂直API

1.1 创建Common层

在这里插入图片描述

1. ICommand接口

  1. ICommand.cs
using MediatR;
namespace BuildingBlocks.CQRS;
public interface ICommand : ICommand<Unit>; 
public interface ICommand<out TResponse> : IRequest<TResponse>;
  1. ICommandHandler.cs
using MediatR;
namespace BuildingBlocks.CQRS;
public interface ICommandHandler<in TCommand>  //无返回值
    : ICommandHandler<TCommand, Unit>
    where TCommand : ICommand<Unit>;
    
public interface ICommandHandler<in TCommand, TResponse> //有返回值
    : IRequestHandler<TCommand, TResponse>
    where TCommand : ICommand<TResponse>
    where TResponse : notnull;

in(Contravariant):只在参数中使用的泛型类型

2. IQuery接口

  1. IQuery.cs
using MediatR;
namespace BuildingBlocks.CQRS;
public interface IQuery<out TResponse> : IRequest<TResponse>  
    where TResponse : notnull;
  1. IQueryHandler:
namespace BuildingBlocks.CQRS;
public interface IQueryHandler<in TQuery, TResponse>
    : IRequestHandler<TQuery, TResponse>
    where TQuery : IQuery<TResponse>
    where TResponse : notnull;

1.2 创建API

在这里插入图片描述

1. 实体

namespace Catalog.API.Models;
public class Product
{
    public Guid Id { get; set; }
    public string Name { get; set; } = default!;
    public List<string> Category { get; set; } = new();
    public string Description { get; set; } = default!;
    public string ImageFile { get; set; } = default!;
    public decimal Price { get; set; }
}

2. Handler

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>
{
    public async Task<CreateProductResult> Handle(CreateProductCommand command, CancellationToken cancellationToken)
    {
        var product = new Product
        {
            Name = command.Name,
            Category = command.Category,
            Description = command.Description,
            ImageFile = command.ImageFile,
            Price = command.Price
        };
        return new CreateProductResult(Guid.NewGuid());        
    }
}

3. endpoint

namespace Catalog.API.Products.CreateProduct;
public record CreateProductRequest(string Name, List<string> Category, string Description, string ImageFile, decimal Price);
public record CreateProductResponse(Guid Id);
public class CreateProductEndpoint : ICarterModule
{
    public void AddRoutes(IEndpointRouteBuilder app)
    {
        app.MapPost("/products",
            async (CreateProductRequest request, ISender sender) =>
        {
            var command = request.Adapt<CreateProductCommand>();

            var result = await sender.Send(command);

            var response = result.Adapt<CreateProductResponse>();

            return Results.Created($"/products/{response.Id}", response);

        })
        .WithName("CreateProduct")
        .Produces<CreateProductResponse>(StatusCodes.Status201Created)
        .ProducesProblem(StatusCodes.Status400BadRequest)
        .WithSummary("Create Product")
        .WithDescription("Create Product");
    }
}

1.3 使用Marten作为ORM

  • Marten只能用于postgresql的ORM使用

二、Redis缓存

2.1 使用缓存装饰器

1. 创建装饰器

  • 给basket添加装饰器,原来的 var basket = await repository.GetBasket(userName, cancellationToken);被其他的方法包裹,这样就被装饰了
 namespace Basket.API.Data;

public class CachedBasketRepository
    (IBasketRepository repository, IDistributedCache cache) 
    : IBasketRepository
{
    public async Task<ShoppingCart> GetBasket(string userName, CancellationToken cancellationToken = default)
    {
        var cachedBasket = await cache.GetStringAsync(userName, cancellationToken);
        if (!string.IsNullOrEmpty(cachedBasket))
            return JsonSerializer.Deserialize<ShoppingCart>(cachedBasket)!;

        var basket = await repository.GetBasket(userName, cancellationToken);
        await cache.SetStringAsync(userName, JsonSerializer.Serialize(basket), cancellationToken);
        return basket;
    }

    public async Task<ShoppingCart> StoreBasket(ShoppingCart basket, CancellationToken cancellationToken = default)
    {
        await repository.StoreBasket(basket, cancellationToken);
        await cache.SetStringAsync(basket.UserName, JsonSerializer.Serialize(basket), cancellationToken);
        return basket;
    }

    public async Task<bool> DeleteBasket(string userName, CancellationToken cancellationToken = default)
    {
        await repository.DeleteBasket(userName, cancellationToken);
        await cache.RemoveAsync(userName, cancellationToken);
        return true;
    }
}

2. 注册装饰器

  • 安装需要的包
     <PackageReference Include="Scrutor" Version="4.2.2" />
    <PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.1" />
  • 注册装饰器
builder.Services.AddScoped<IBasketRepository, BasketRepository>(); //原来的方法
builder.Services.Decorate<IBasketRepository, CachedBasketRepository>();  //装饰过后带redis缓存的


//注册redis
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
    //options.InstanceName = "Basket";
});  
  • 配置redis 的ConnectionString
 appsettings.json

2.2 创建docker-compose

1. docker-compose

  1. docker-compose.yml 用来说明微服务需要的Container有哪些
version: '3.4'
services:
  catalogdb:
    image: postgres
  basketdb:
    image: postgres
  distributedcache:
    image: redis
  catalog.api:
    image: ${DOCKER_REGISTRY-}catalogapi
    build:
      context: .
      dockerfile: Services/Catalog/Catalog.API/Dockerfile
  basket.api:
    image: ${DOCKER_REGISTRY-}basketapi
    build:
      context: .
      dockerfile: Services/Basket/Basket.API/Dockerfile
volumes:
  postgres_catalog:
  postgres_basket:

2. docker-compose.override

  • 用来配置本地具体环境
version: '3.4'

services:
  catalogdb:
    container_name: catalogdb
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=CatalogDb
    restart: always
    ports:
        - "5432:5432"
    volumes:
      - postgres_catalog:/var/lib/postgresql/data/ 
      
  basketdb:
    container_name: basketdb
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=BasketDb
    restart: always
    ports:
        - "5433:5432"
    volumes:
      - postgres_basket:/var/lib/postgresql/data/ 

  distributedcache:
    container_name: distributedcache
    restart: always
    ports:
      - "6379:6379"

  catalog.api:
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_HTTP_PORTS=8080
      - ASPNETCORE_HTTPS_PORTS=8081
      - ConnectionStrings__Database=Server=catalogdb;Port=5432;Database=CatalogDb;User Id=postgres;Password=postgres;Include Error Detail=true
    depends_on:
      - catalogdb
    ports:
      - "6000:8080"
      - "6060:8081"
    volumes:
      - ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro
      - ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:ro

  basket.api:
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_HTTP_PORTS=8080
      - ASPNETCORE_HTTPS_PORTS=8081
      - ConnectionStrings__Database=Server=basketdb;Port=5432;Database=BasketDb;User Id=postgres;Password=postgres;Include Error Detail=true
      - ConnectionStrings__Redis=distributedcache:6379
    depends_on:
      - basketdb
      - distributedcache
    ports:
      - "6001:8080"
      - "6061:8081"
    volumes:
      - ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro
      - ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:ro

网站公告

今日签到

点亮在社区的每一天
去签到