zoukankan      html  css  js  c++  java
  • eShopOnWeb 知多少

    构建现代Web应用

    1.引言

    eShopOnWeb是基于ASP.NET Core构建,官方创建这样一个示例项目的目的,我想无非以下几点:

    1. 推广ASP.NET Core
    2. 指导利用ASP.NET Core如何进行架构设计
    3. 普及架构设计思想

    eShopOnWeb 与另外一个eShopOnContainers互相补充。eShopOnContainers是基于微服务和容器技术的应用程序架构,支持多重部署。而eShopOnWeb相较于它就简单的多,其是基于传统Web应用开发,仅支持单一部署。

    本文就简单梳理下自己的所学所得。

    2.MPA Or SPA

    eShopOnWeb的示例项目中包含两个Web项目,一个是基于MVC创建的MPA多页面应用,一个是基于Razor创建的SPA单页面应用。在此之间我该如何选择呢?

    1. 是否需要丰富的交互行为?
    2. 是否足够的前端技术积累?
    3. 是否主要通过API进行交互?

    决策表:Mpa or Spa

    3. 架构设计

    eShopOnWeb中应用了DDD和整洁架构的部分思想,值得了解一下。

    3.1 架构原则

    关注点分离:简称SOP。在分层架构设计中,关注点分离是核心设计思想,每一层独自负责不同的职责。从架构上讲,可以通过将核心业务与基础设施和用户界面逻辑分离来实现。该原则旨在避免紧耦合,又可确保各个模块独立发展。

    封装:封装的是什么?是对象的状态和行为。外部对象无需关注其内部的实现机制。
    在类中,通过使用访问修饰符来限制外部的访问来实现封装。 如果外部想要操纵对象的状态,它应该通过定义良好的函数(或属性设置器)来实现,而不是直接访问对象的私有状态。
    而不同模块之间通过公开定义良好的接口进行方法调用,来实现封装。以隔离内部的实现机制。通过封装来确保应用程序间不同部分之间的隔离,正确使用封装有助于在应用程序设计中实现松耦合和模块化。

    依赖倒置:简称DIP。高层模块不应该依赖低层模块,均应该依赖与抽象;抽象不应该依赖于细节;细节应该依赖于抽象。DIP是构建松耦合应用的关键部分,从而确保应用程序模块化,更易于测试和维护。 通过遵循DIP,可以应用依赖注入。

    显式依赖:方法和类应明确指定所需的协作对象(依赖)以确保正常运行。简单来说,对于类而言,提供明确的构造函数(即在构造函数参数中指定该类需要正常工作所需的依赖对象),以便调用者正确传参以正确实例化对象。

    单一职责:简称SRP。SRP作为面向对象设计的原则之一,也适用于架构原则。其与SOP类似。它强调对象应该只有一个责任,他们只应该仅有一个改变的理由。换言之,对象应该改变的唯一情况是它的职责需要被更新。遵守该原则,可以编写松耦合和模块化的应用。因为大量的新的行为都应该创建新类去实现,而不是添加到已经存在的类中。添加新类永远比修改一个类安全,因为尚无代码依赖于新类。
    在复杂的大型应用中,可以将SRP应用到分层应用的各个层。展现职责应保留在UI项目中,而数据访问职责应保留在基础设施项目中, 业务逻辑应该保留在应用程序核心项目中。如此,即易于测试又可以独立于其他职责持续演化。
    该原则的更高级应用,就是微服务了。每个微服务负责独立的职责。

    摒弃重复:当出现重复时,应该实施重构。避免当功能改进时,需要同时修改多个部分。

    透明持久化:要求可以轻松切换持久化技术,而实现持久化无感知(透明持久化)。

    限界上下文:该概念是DDD战略设计的一部分,通过限界上下文来划分领域,作为领域的显式边界,为领域提供上下文语境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。

    3.2. 传统分层架构和整洁架构

    传统的分层架构是大家所熟知的三层架构。
    传统三层架构

    三层架构示例

    这样的架构的缺点是:

    1. 依赖关系由上至下,不易解耦
    2. 不易测试,需要测试数据库

    那如何解决三层架构的问题呢,借助【依赖倒置原则】。
    DDD的分层架构思想和整洁架构中都是借助【依赖倒置原则】实现层与层之间强依赖关系的解耦。我们来看下整洁架构:

    整洁架构——洋葱视图

    从该洋葱视图中我们可以看到:

    1. 依赖关系由外而内。
    2. 处于核心的是实体和接口,不依赖任何其他项。其次是领域服务,仅依赖实体和接口,也相对独立。它们统称为应用程序内核
    3. 应用程序内核之外是基础架构层和展现层,彼此也不一定依赖。

    由于应用程序内核不依赖于基础设施层,所以可以很容易编写单元测试。
    单元测试位置
    由于UI层也不直接依赖于基础设施层,所以我们可以轻松置换基础设施层的实现(比如使用内存数据库),以进行集成测试。
    集成测试位置

    整洁架构——水平视图

    下面我们就来看看eShopOnWeb是如何应用整洁架构的。

    4. 项目结构

    首先我们看下模板架构的项目结构。
    eShopOnWeb Solution

    从上图来看其项目结构十分简单,简单的三层,加上三个测试项目。
    三层对应:

    1. ApplicationCore:领域层
    2. Infrastructure:基础设施层
    3. Web/WebRazorPages:展现层

    DDD使用的传统分层架构

    其实该项目架构是DDD经典四层架构,只不过其将应用层集成到展现层中去了。
    Web应用服务

    4.1 基础设施层

    主要提供通用的基础服务和持久化。
    Infrastructure

    从上图的代码结构我们可以看出:

    1. 在Data文件夹下定义了用于持久化的商品目录数据库上下文CatalogContext和泛型仓储EfRepository
    2. Identity文件夹下定义了身份数据库上下文的。
    3. Logging文件夹定义了一个日志适配器。
    4. Services定义了一个通用的邮件发送基础服务。

    4.2. 领域层

    领域层是一个项目的核心,用来定义业务规则并实现。其主要用来实体、值对象、聚合、仓储、领域服务和领域事件等。
    ApplicationCore

    从上图来看:

    1. Entities文件夹下定义了三个聚合根和相关的实体及值对象。
    2. Exceptions文件夹定义了公共的异常。
    3. Interfaces文件夹定义了系列接口。
    4. Services文件夹定义了两个领域服务。
    5. Specifications文件夹下是实现的规约模式。

    4.2.1. 聚合根的相关实现

    这里我们来看下聚合根的相关定义和实现。

    ///抽象的聚合根空接口
    public interface IAggregateRoot
        { }
    //所有的实体基类
    public class BaseEntity
        {
            public int Id { get; set; }
        }
    
    //购物车聚会根
    public class Basket : BaseEntity, IAggregateRoot
    {
        public string BuyerId { get; set; }
        private readonly List<BasketItem> _items = new List<BasketItem>();
        public IReadOnlyCollection<BasketItem> Items => _items.AsReadOnly();
    
        public void AddItem(int catalogItemId, decimal unitPrice, int quantity = 1)
        {
            if (!Items.Any(i => i.CatalogItemId == catalogItemId))
            {
                _items.Add(new BasketItem()
                {
                    CatalogItemId = catalogItemId,
                    Quantity = quantity,
                    UnitPrice = unitPrice
                });
                return;
            }
            var existingItem = Items.FirstOrDefault(i => i.CatalogItemId == catalogItemId);
            existingItem.Quantity += quantity;
        }
    }
    

    从这个实现中我们可以学习到:

    通过定义一个空的接口IAggregateRoot,要求所有的聚会根来实现它。

    这样做的体现了什么思想:

    1. 面向接口编程
    2. 约定大于配置
    3. 依赖注入

    通过定义一个BaseEntity,要求所有的实体继承它。

    为什么这样做?

    1. 因为实体的特征是具有唯一的身份标识,所以通过在父类来定义Id属性来实现。这也就是层超类型的实现方式。

    这样做有什么缺点?
    因为所有实体的主键类型不一定都是int类型,所以这个基类型最好改成泛型。

    Basket聚合根中将Items定位为Readonly,是为了封装集合,避免子项被其他地方更改。

    4.2.2. 仓储的相关实现

    仓储是用来透明持久化领域对象的。

    public interface IRepository<T> where T : BaseEntity
    {
        T GetById(int id);
        T GetSingleBySpec(ISpecification<T> spec);
        IEnumerable<T> ListAll();
        IEnumerable<T> List(ISpecification<T> spec);
        T Add(T entity);
        void Update(T entity);
        void Delete(T entity);
    }
    public interface IAsyncRepository<T> where T : BaseEntity
    {
        Task<T> GetByIdAsync(int id);
        Task<List<T>> ListAllAsync();
        Task<List<T>> ListAsync(ISpecification<T> spec);
        Task<T> AddAsync(T entity);
        Task UpdateAsync(T entity);
        Task DeleteAsync(T entity);
    }
    

    从以上代码我们可以学到两点:

    1. 面向接口编程
    2. 职责分离,同步异步接口分离。

    4.2.3. 领域服务相关实现

    领域服务用来实现业务逻辑的。

    public interface IOrderService
    {
        Task CreateOrderAsync(int basketId, Address shippingAddress);
    }
    public class OrderService : IOrderService
    {
        private readonly IAsyncRepository<Order> _orderRepository;
        private readonly IAsyncRepository<Basket> _basketRepository;
        private readonly IAsyncRepository<CatalogItem> _itemRepository;
        public OrderService(IAsyncRepository<Basket> basketRepository,
            IAsyncRepository<CatalogItem> itemRepository,
            IAsyncRepository<Order> orderRepository)
        {
            _orderRepository = orderRepository;
            _basketRepository = basketRepository;
            _itemRepository = itemRepository;
        }
        public async Task CreateOrderAsync(int basketId, Address shippingAddress)
        {
            var basket = await _basketRepository.GetByIdAsync(basketId);
            Guard.Against.NullBasket(basketId, basket);
            var items = new List<OrderItem>();
            foreach (var item in basket.Items)
            {
                var catalogItem = await _itemRepository.GetByIdAsync(item.CatalogItemId);
                var itemOrdered = new CatalogItemOrdered(catalogItem.Id, catalogItem.Name, catalogItem.PictureUri);
                var orderItem = new OrderItem(itemOrdered, item.UnitPrice, item.Quantity);
                items.Add(orderItem);
            }
            var order = new Order(basket.BuyerId, shippingAddress, items);
            await _orderRepository.AddAsync(order);
        }
    

    从以上代码我们可以学习到:

    1. 依赖注入
    2. 领域服务负责实现真正的业务逻辑

    4.3. 应用层和展现层

    如上面所阐述,在示例项目中应用层和展现层合二为一。应用层负责展现层与领域层之间的协调,协调业务对象来执行特定的应用程序。

    5. 面向切面编程(AOP)

    eShopOnWeb中也提到了AOP,介绍了在ASP.NET Core中如何应用过滤器来进行AOP,比如:身份验证、模型验证、输出缓存和错误处理等。
    通过过滤器和请求管道执行

    执行顺序

    5. 简明DDD

    在eShopOnWeb中,也对DDD的概念,是否使用,何时使用,何时不用,都略有介绍。这里就摘录一二,当然也可以参考我之前的写的DDD理论学习系列

    结论

    1. DDD首先是一个方法论,其注重于领域的合理建模,分为战略建模和战术建模。
    2. 如果你不知道你需要它,那么你可能不需要它。
    3. 如果你不知道到DDD用于解决什么问题,那么你可能没有遇到这些问题。
    4. DDD倡导者也经常指出其仅适用于大型项目 (>6个月)。

    相关概念

    1. DDD是用来对真实世界系统或流程的建模。
    2. 使用DDD时,你需要和领域专家紧密合作,领域专家能够解释真实的系统该如何运行。在和领域专家的交流中确定通用语言,其主要用来描述系统中的一些概念。而之所以是通用,是因为不管是开发人员还是领域专家都应能够读懂。而通用语言描述的概念将构成面向对象设计的基础。其体现在代码中的理想状态是代码即设计

    战术

    1. 值对象:不可变。
    2. 实体:具有唯一标识符可变。
    3. 聚会根:在DDD中,用来表示整体与部分的关系,聚合是将相关联的领域对象进行显式分组,来表达整体的概念(也可以是单一的领域对象)。比如将表示订单与订单项的领域对象进行组合,来表达领域中订单这个整体概念。
    4. 仓储:一种持久化的模式,用于隔离具体持久化措施,实现透明持久化。
    5. 工厂:用于对象的创建。
    6. 服务:应用服务和领域服务。领域服务负责业务逻辑,应用服务用于表达业务用例和用户故事。

    战略

    1. 限界上下文:来为领域提供上下文语境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。
    2. 上下文映射图:限界上下文之间的关联关系。

    6. 应用测试

    在eShopOnWeb中,还示例了三个测试项目,来指导我们合理的进行测试。
    单元测试、集成测试和功能测试的区别

    7. 总结

    总体而言,示例项目简单容易理解,也主要是为了便于推广和演示。但里面涉及的知识点并没有想象的那么简单,从架构原则到设计和应用,每一个环节都包含不简单的知识体系。

    所以等什么呢?结合示例项目和官方文档使用 ASP.NET Core 和 Azure 构建新式 Web 应用程序开始学习吧,相信你也会收获颇丰。

  • 相关阅读:
    VOA 2009/11/02 DEVELOPMENT REPORT In Kenya, a Better Life Through Mobile Money
    2009.11.26教育报道在美留学生数量创历史新高
    Java中如何实现Tree的数据结构算法
    The Python Tutorial
    VOA HEALTH REPORT Debate Over New Guidelines for Breast Cancer Screening
    VOA ECONOMICS REPORT Nearly Half of US Jobs Now Held by Women
    VOA ECONOMICS REPORT Junior Achievement Marks 90 Years of Business Education
    VOA 2009/11/07 IN THE NEWS A Second Term for Karzai; US Jobless Rate at 10.2%
    Ant入门
    Python 与系统管理
  • 原文地址:https://www.cnblogs.com/sheng-jie/p/9616675.html
Copyright © 2011-2022 走看看