思考一种好的架构(七)

 

先来看看商品业务服务库

 

 

有控制器、有实体、有自己的服务、有配置

依赖:

 

 这也是所有业务服务库的大体依赖

必选:

中介者服务(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服务是在中介者服务中的,中介者服务会在后续说到






 

 

 

 

 

 

 

posted @ 2020-04-01 11:05  AnAng  阅读(151)  评论(0编辑  收藏  举报