(一)微服务(垂直AP/分布式缓存/装饰器Pattern)
文章目录
- 项目地址
- 一、创建第一个垂直API
- 1.1 创建Common层
- 1. ICommand接口
- 2. IQuery接口
- 1.2 创建API
- 1. 实体
- 2. Handler
- 3. endpoint
- 1.3 使用Marten作为ORM
- 二、Redis缓存
- 2.1 使用缓存装饰器
- 1. 创建装饰器
- 2. 注册装饰器
- 2.2 创建docker-compose
- 1. docker-compose
- 2. docker-compose.override
项目地址
- 教程作者:
- 教程地址:
- 代码仓库地址:
- 所用到的框架和插件:
dbt
airflow
一、创建第一个垂直API
1.1 创建Common层
1. ICommand接口
ICommand.cs
using MediatR;
namespace BuildingBlocks.CQRS;
public interface ICommand : ICommand<Unit>;
public interface ICommand<out TResponse> : IRequest<TResponse>;
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接口
IQuery.cs
using MediatR;
namespace BuildingBlocks.CQRS;
public interface IQuery<out TResponse> : IRequest<TResponse> where TResponse : notnull;
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
docker-compose.yml
用来说明微服务需要的Container有哪些
version: '3.4'
services:catalogdb:image: postgresbasketdb:image: postgresdistributedcache:image: rediscatalog.api:image: ${DOCKER_REGISTRY-}catalogapibuild:context: .dockerfile: Services/Catalog/Catalog.API/Dockerfilebasket.api:image: ${DOCKER_REGISTRY-}basketapibuild:context: .dockerfile: Services/Basket/Basket.API/Dockerfile
volumes:postgres_catalog:postgres_basket:
2. docker-compose.override
- 用来配置本地具体环境
version: '3.4'services:catalogdb:container_name: catalogdbenvironment:- POSTGRES_USER=postgres- POSTGRES_PASSWORD=postgres- POSTGRES_DB=CatalogDbrestart: alwaysports:- "5432:5432"volumes:- postgres_catalog:/var/lib/postgresql/data/ basketdb:container_name: basketdbenvironment:- POSTGRES_USER=postgres- POSTGRES_PASSWORD=postgres- POSTGRES_DB=BasketDbrestart: alwaysports:- "5433:5432"volumes:- postgres_basket:/var/lib/postgresql/data/ distributedcache:container_name: distributedcacherestart: alwaysports:- "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=truedepends_on:- catalogdbports:- "6000:8080"- "6060:8081"volumes:- ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro- ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:robasket.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:6379depends_on:- basketdb- distributedcacheports:- "6001:8080"- "6061:8081"volumes:- ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro- ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:ro