思考一种好的架构(七)
先来看看商品业务服务库
有控制器、有实体、有自己的服务、有配置
依赖:
这也是所有业务服务库的大体依赖
必选:
中介者服务(Mediator)
数据库访问(ORM.Chloe)
路由(Route)
可选:
对象映射服务(AutoMapper)
扫描服务(ReferenceScan)
库存业务服务库(Stock)
控制器:
[ApiController] [APIRouth("Product")] public class ProductController : Controller { IProduct productServer; public ProductController(IProduct productServer) { this.productServer = productServer; } /// <summary> /// 获取全部商品信息 /// </summary> /// <param name="maxNumber">库存最大数量</param> /// <returns></returns> [HttpGet] public IActionResult GetAll(int? maxNumber) { var result =productServer.GetAll(maxNumber); return Json(result); } /// <summary> /// 获得单个商品信息 /// </summary> /// <param name="ID">商品ID</param> /// <returns></returns> [HttpGet] public IActionResult Get(int ID=0) { var result = productServer.GetSingle(ID); return Json(result); } }
其中
APIRouth特性头是编写的一个特性
/// <summary> /// API路由 /// </summary> public class APIRouth : Microsoft.AspNetCore.Mvc.RouteAttribute { /// <summary> /// 设计一个API的路由 /// </summary> /// <param name="area">区域名称</param> public APIRouth(string area) : base($"api/{area}/[controller]/[action]") { } }
这是为了达到统一API路径的目的
控制器很简单
只执行调用IProduct 服务的功能,在后续开发中,他也是比较贫血的一层
可以预料的是,它的作用仅仅只是为了提供一个访问入口且写明注释,并调用了服务
至于表单验证,响应模型之类的我们将会在中间件中完成,可以会有困难,但还是尽量中间件中完成,如果不行那么将会仍在过滤器或者父类控制器完成,尽量保证不会在业务中出现这种重复性代码
实体:
[Table(Name = "Product")] public class ProductEntity { [ColumnAttribute(IsPrimaryKey = true)] [AutoIncrement] public int ID { get; set; } public string Title { get; set; } public string Describe { get; set; } }
业务处理服务:
public class ProductServer : IProduct { IDbContext DC; IStock stock; IMapper mapper; public ProductServer(IDbContext DC, IStock stock,IMapper mapper) { this.DC = DC; this.stock = stock; this.mapper = mapper; } public List<ProductInfoDTO> GetAll(int? maxNumber) { var result = DC.Query<ProductEntity>().RightJoin(stock.StockNumberQuery(null, maxNumber), (c, z) => c.ID.Equals(z.ProductID)) .Select((x, z) => new ProductInfoDTO() { Describe = x.Describe, ID = x.ID, StockNumber = z.Number, Title = x.Title }); return result.ToList(); } public ProductInfoDTO GetSingle(int ID) { return DC.Query<ProductEntity>().InnerJoin(stock.GetSingle(ID), (c, z) => c.ID.Equals(z.ProductID)).Select((x, z) => new ProductInfoDTO() { Describe = x.Describe, ID = x.ID, StockNumber = z.Number, Title = x.Title }).FirstOrDefault(x => x.ID.Equals(ID)); ; } }
DC 数据库上下文
stock 库存服务
mapper (AutoMapper) 对象映射服务
这里有一个跨服务的联合查询
在数据库中 商品和库存是两个关联的表
代码中通过
RightJoin
InnerJoin
来进行链表查询
public interface IStock { /// <summary> /// 库存查询 /// </summary> /// <param name="minNumber">最小库存</param> /// <param name="maxNumber">最大库存</param> /// <returns></returns> IQuery<StockDTO> StockNumberQuery(int? minNumber, int? maxNumber); /// <summary> /// 库存查询 /// </summary> /// <param name="product">商品ID</param> /// <returns></returns> IQuery<StockDTO> GetSingle(int productID); /// <summary> /// 消减库存 /// </summary> /// <param name="productID">商品ID</param> /// <param name="number">数量</param> /// <returns></returns> bool ReducedInventory(int productID,int number); }
跨库查询也是一个非常大的问题,这里我们还只是一个同数据库不同服务的跨服务查询,后续的跨服务跨库查询才是真正难以解决的问题
库存服务返回了一个IQuery查询对象,方便Chloe进行链表操作
最后在Product服务中去执行关联和返回查询实体,
这里没有用到mapper,因为Chloe的Select已经足够使用,没必要再去添一行代码去使用mapper
启动执行:
public class StartExecute : BaseStartExecute { public override void Execute() { MapperPost.CreateMap(x => x.CreateMap<ProductInfoDTO, ProductEntity>()); MapperPost.CreateMap(x => x.CreateMap<ProductEntity, ProductInfoDTO>().ForMember(dest => dest.StockNumber, opt => opt.Ignore())); } }
Mapper需要在启动的时候也就是ConfigureServices函数中就进行调用,这会有个问题,大家争抢ConfigureServices函数执行顺序,变得非常难以管理,所以
StartExecute出现了,它会在所有服务调用ConfigureServices前调用,并逐个执行Execute函数,这样一来,就不用关注ConfigureServices函数的执行顺序啦
具体代码后续在说
启动类:
public static class Startup { public static void ConfigureServices(IServiceCollection services) { services.AddTransient<IProduct, ProductServer>(); } public static void Configure(IApplicationBuilder app, IHostingEnvironment env) { } }
在这里注册了IProduct服务,
IProduct服务是在中介者服务中的,中介者服务会在后续说到