文章目录
项目地址
- 教程作者:
- 教程地址:
- 代码仓库地址:
- 所用到的框架和插件:
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: 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