.net架构设计读书笔记--第三章 第9节 域模型实现(ImplementingDomain Model)

    我们长时间争论什么方案是实现域业务领域层架构的最佳方法。最后,我们用一个在线商店案例来说明,其中忽略了许多之前遇到的一些场景。在线商店对很多人来说更容易理解。

一、在线商店项目简介

1. 用例选择

Use-case

Description

Registers to the site

The user fills in the application form and becomes an official customer of the I-Buy-Stuff site.

Log in to start using the site

The user enters credentials and logs in. Credentials can be entered directly to the site or via a couple of social networks.

Subscribe to the newsletter

The user adds an email address to receive the newsletter.

Search an order

The user indicates the number of one of her orders and reviews details such as estimated delivery date, items, and costs.

Place an order

The user fills a shopping cart with a list of products and then proceeds with

making payment and entering the details related to delivery

2. 方法选择

    在线商城的需求,几乎所有的开发人员都或多或少的了解。在需要分析后和通常语言的定义下,将在线商城清晰的定义为一些实体。可以将项目中的类分开创建为类库。域模型将定义为以下域实体:Admin, Customer, Order, OrderItem, Product, Subscriber, 和FidelityCard。

 

3. 设计在线商城项目结构

    前面的章节中专注于探讨分层结构在DDD中的作用,现在看下实际项目中是如何使用的。下图是VS2013中的项目结构

    图中可以的地到Demain Layer,Infrastructure Layer, 和site。IBuyStuff解决方案中,site目录中实际上就是一个asp.net mvc应用程序。从逻辑上划分,可以将系统分为前端和后端两部分。Demain Layer 和Infrastructure Layer是后端的部分,后端尽可能多的在同一个上下文中处理前端的动作。

 

4. 技术选择

    IBuyStuff解决方案使用了一系列.net技术,大部分通过NuGet包引入。前面已经提到,前端是使用了ASP.NET Identity 的asp.net mvc5的应用程序。Twitter Bootstrap技术也用户界中使用。针对于移动视图,使用界面使用了WURFL来感知终端设备,Asp.net通过路由来展现View。

    后端的持久化使用了Entity Framework Code First实现。在示例代码中本地的SqlServer实例在web站点的App_Data目录下。最后,项目中所有的依赖注入都是使用Microsoft Patterns and Practices Unity。

5. 边界上下文设计

    I-Buy-Stuff在线商城是一个刻意简单小型、上下文边界中业务不需要面对额外的需求。演示一个广义的DDD架构它可能不是最佳的例子,但别一方面,一个太复杂的例子又太难讲解。

在域上下文中分解业务,一个合理简单的方案,将业务分为以下几个边界上下文:

  • MembershipBC
  • OrderingBC
  • CatalogBC

MembershipBC用于身份认证和用户账户管理。OrderingBC用于管理购物车和订单处理,CatalogBC用于后台管理,如产品更新、添加、删除,更新库存等。

 

OrderingBC最可能是一个Asp.net Mvc应用程序,如在线商城。Membership在同一个应用中作为不同的功能模块独立存在。CatalogBC是管理系统,更多是数据库的CRUD操作,没在必要使用DDD设计。

6. 边界上下文间的通信

    Product的增删改查何时通知OrderingBC上下文边界?OrderingBC要对数据尽可能频繁读取来获取CatalogBC对数据库的更新是不太可能的,别外一种方法,当CatalogBC更新时,同时调用一个OrderingBC的RPC。在大型复杂的系统中,你甚至会用Service bus将架构中许多小模块连接起来。

7. 添加其它的上下文边界

    随着商城的复杂度成长,你要添加其它的上下文,比如:ShippingBC,PricingBC,PurchaseBC等。你可以将他们放到同下big上下文,可以将他们分割成多个小的上下文。这儿没有什么硬性指标来决策怎么做。

8. I-Buy-Stuff解决方案中的上下文图表

    如下图所示,I-Buy-Stuff上下文是一个ASP.NET MVC应用程序,它要在域模型架构上运行,是一个相当大的上下文,国为它包含了会员、订购逻辑、甚至是包含了购买上下文。I-Buy-Stuff上下文承载着大部分业务逻辑,Shipping和Purchase被实现为服务形式。

 

 

 

二、高大尚的域模型建模

    现代软件的根本问题是,开发都常常宁愿关注代码而不愿意面对模型设计,这种方法本身没有错,但是你将面对的是难以管理的复杂度。如果你没有进行域模型设计的情况下写出了成功的代码,这样做有错吗?当然没错,但你有可能在复杂度增长时暴露出问题。它可能出现在下一个项目或同一个项目中的需求增长和变更。

1. anemic model 弱模型

    DDD权威指出没有模型没有必要是弱模型。软件设计中的面向对象指出,一个类是对数据和行为的封装,而一个模型只是用于映射数据库,基本上没有行为方法,这样的类被称之为弱类 anemic model。

    业务逻辑的位置

实际上,面向行为本质上是找出业务逻辑模块的位置。

class Invoice

{

public string Number {get; private set;}

public DateTime Date {get; private set;}

public Customer Customer {get; private set;}

public string PayableOrder {get; private set;}

public ICollection<InvoiceLine> Lines {get; private set;}

...

}

上面的Invoice类中没有方法,是错误的写法?还是弱模型的一部分?如果对象仅没有方法,它也不一定是一个弱模型。弱模型是指将业务逻辑放到了模型外的地方实现。

2. Entities scaffolding

    一个DDD实体应该是一个同时包含数据和行为的类。一个实体可能有公有属性,但他不会被用作为一个数据容器。下面是DDD实体的特性

  • 良好的唯一性定义
  • 行为通过方法来表示,不管是公有方法还是非公有方法
  • 通过只读属性公开状态
  • 用值对象代替很少用的基元类型
  • 使用工厂方法来代替多个构造函数

 

3.Value objects scaffolding

    与entity类似,value object是由一系列数据组成的类。行为对值对象来说是不需要的。值对象也可以有方法,但是这些方法都是一些辅助类方法。于entity不同,value object不需要唯一性,它们没有可变状态,它们只用于存储数据。如DateTime类型的构造函数。

4. 明确合集

将话题转到I-Buy-Stuff这个示例应用程序上,I-Buy-Stuff是一个简单的在线商城,下图反应出应用中实体的关系。在站点中,我们认为只有少量的用例,如:搜索,下单。Customer、Order、Product是合集,Customer和Produc是单件实体合集。

  •  Customer aggregate

    Customer的特点是有一些个人数据,如name,address,username,或payment details。我们明确假设I-Buy-Stuff系统中只有一种类型用户,不用做任何区分。如:法人客户可能和个人客户,法人客户下可能包含多个个人客户。I-Buy-Stuff选择第3种表示方法,不区分。

Customer定义

public class Customer : IAggregateRoot

{

    public static Customer CreateNew(Gender gender, string id,string firstname, string lastname, string email)

    {

var customer = new Customer {

CustomerId = id,

Address = Address.Create(),

Payment = NullCreditCard.Instance,

Email = email,

FirstName = firstname,

LastName = lastname,

Gender = gender

};

return customer;

}

public string CustomerId { get; private set; }

public string PasswordHash { get; private set; }

public string FirstName { get; private set; }

public string LastName { get; private set; }

public string Email { get; private set; }

public Gender Gender { get; private set; }

public string Avatar { get; private set; }

public Address Address { get; private set; }

public CreditCard Payment { get; private set; }

public ICollection<Order> Orders { get; private set; }

// More methods here such as SetDefaultPaymentMode.

// A customer may have a default payment mode (e.g., credit card), but

// different orders can be paid in different ways.

...

}

其它几个合集定义……

 

5. 实体持久化 Persisting the model

    域模型要支持持久化,常见的是用O/RM来实现,如Entity Framework Code-First 方法实现。本质上Entity Framework有两种实现风格:Database First 与Code First。另外还有第三种风格,Model First,它是一种混合风格。Database-First方法中Entity Framework读取数据库结构,生成和一系列partial类型的弱类(anemic class),可以使用partial来扩展类。Code-First风格是生成一系列类,如I-Buy-Stuff定义的合集,然后添加一个额外的层来映射到数据库表。这个映射层告诉O/RM关于数据库信息,及如何CRUD数据。I-Buy-Stuff示例中使用的是Code-First风格。

Code-First通过继承DbContext类为中心实现持久化。

public class DomainModelFacade : DbContext

{

    static DomainModelFacade()

    {

        Database.SetInitializer(new SampleAppInitializer());

    }

    public DomainModelFacade() : base("naa4e - 09")

    {

        Products = base.Set<Product>();

        Customers = base.Set<Customer>();

        Orders = base.Set<Order>();

        FidelityCards = base.Set<FidelityCard>();

    }

    public DbSet<Order> Orders { get; private set; }

    public DbSet<Customer> Customers { get; private set; }

    public DbSet<Product> Products { get; private set; }

    public DbSet<FidelityCard> FidelityCards { get; private set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)

    {

...

}

}

 

    Mapping properties to columns

    Code First中有两种方法将properties映射到columns。可以在demain class中使用data annotation attributes,也可以使用Code-First API自带的configuration classes来重写OnModelCreating方法轻松实现。如:

modelBuilder.ComplexType<Money>();

modelBuilder.ComplexType<Address>();

modelBuilder.ComplexType<CreditCard>();

modelBuilder.Configurations.Add(new FidelityCardMap());

modelBuilder.Configurations.Add(new OrderMap());

modelBuilder.Configurations.Add(new CustomerMap());

modelBuilder.Configurations.Add(new OrderItemMap());

modelBuilder.Configurations.Add(new CurrencyMap());

 

 

三、实现业务逻辑

    很多情况下,并不所有的业务逻辑都需要融入到域模型的类里。而非业务逻辑的功能,如持久化功能也要添加到域模型中。在I-Buy-Stuff项目中有两个主要的业务逻辑:查询订单和订单下单。

1.查询订单

    下面的代码中Controller接受一个查找订单的用户请求,controller方法中使用一个Service来获取订单数据。

public ActionResult SearchResults(int id)

{

var model = _service.FindOrder(id, User.Identity.Name);

return View(model);

}

public SearchOrderViewModel FindOrder(int orderId, string customerId)

{

    var order = _orderRepository.FindByCustomerAndId(orderId, customerId);

    if (order is NullOrder)

        return new SearchOrderViewModel();

    return SearchOrderViewModel.CreateFromOrder(order);

}

application service中FindOrder方法调用域服务处仓储服务来获取订单。

 

2. 订单下单

    当用户在站点上点击购买时,商城系统反回一个用户界面,用于创建订单,另外,还有添加到购物车等方法

public ActionResult New()

{

    var customerId = User.Identity.Name;

    var shoppingCartModel = _service.CreateShoppingCartForCustomer(customerId);

    shoppingCartModel.EnableEditOnShoppingCart = true;

    SaveCurrentShoppingCart(shoppingCartModel);

    return View("shoppingcart", shoppingCartModel);

}

 

    //添加到购物车

public ActionResult AddToShoppingCartCommand(int productId, int quantity = 1)

{

    var cartModel = RetrieveCurrentShoppingCart();

    cartModel = _service.AddProductToShoppingCart(cart, productId, quantity);

    SaveCurrentShoppingCart(cartModel);

    return RedirectToAction("AddTo");

}

 

public ShoppingCartViewModel AddProductToShoppingCart(ShoppingCartViewModel cart, int productId, int quantity)

{

    var product = (from p in cart.Products where p.Id == productId select p).Single();

    cart.OrderRequest.AddItem(quantity, product);

    return cart;

}

 

 

 

 

posted @ 2016-03-12 00:47  徐某人  阅读(364)  评论(0编辑  收藏  举报