您的位置:首页 > 编程语言

领域驱动设计之代码优先-领域层设计-7 (翻译)

2013-03-21 10:08 537 查看
3.6.- 实体类的领域逻辑(非贫血领域)

在领域驱动设计中需要把逻辑关联到实体的内部操作中。如果实体类只是用来数据结构,所有的领域逻辑都放在

领域服务中,这会构成一个反模式叫做”贫血领域模型“。见http://www.martinfowler.com/bliki/AnemicDomainModel.html

该反模式解释了对于面向领域应用,实体模型不应包含数据。这个方法对于使用ADO.NET Datasets的人很典型。

但是如果我们审视实体的业务逻辑,我们会发现大多数领域实体都它内部行为。有时只是一些验证数据的方法,

有时是进行计算的业务方法,很多时候是实体内部重要的业务逻辑。如果我们不为实体类添加行为,我们会不得

把行为放在领域服务中,这会是一种不好的设计抉择因为领域逻辑在会更难维护,业务规则的重用会更困难。简言之

我们要做的是提供实体的”面向对象的情景“,就像Martin Fowler在解释”贫血模型“时提到的。

因此,我们应该在每个实体类中添加相关的内部业务逻辑。如果我们用代码优先的POCO实体类,好消息是可以

直接在实体类中添加领域逻辑。如果使用STE或T4模板的POCO类,就应该添加领域逻辑到部分类中。这是另一个

为什么代码优先更适合领域驱动设计的理由。

例如,下面的BankAccount实体类在内部添加了领域逻辑。尤其是,进行的操作是”账户提款“,在操作前进行必要

的检查:
//POCO Domain Entity
//
//
public class BankAccount : Entity,IValidatableObject
{

public class BankAccount
:Entity,IValidatableObject
{
public string BankAccountNumber { get; set; }
public decimal Balance { get; private set; }
public bool Locked { get; private set; }
public Guid CustomerId { get; set; }
public virtual Customer Customer { get; private set; }

//Ommitted BankAccountActivities
//…

public void Lock()
{
if (!Locked)
Locked = true;
}
POCO-Entity with Domain logic
Entity data properties

public void UnLock()
{
if (Locked)
Locked = false;
}

public void WithdrawMoney(decimal amount,string reason)
{
if ( amount < 0 )  throw new
ArgumentException(Messages.exception_BankAccountInvalidWithdrawAmount);

//WithdrawMoney is a term of our Ubiquitous Language.
//Means deducting money to this account
if (CanBeWithdrawed(amount))
{
checked
{
this.Balance -= amount;

//anotate activity
this.BankAccActivity.Add(new BankAccountActivity()
{
Date = DateTime.UtcNow,
Id = IdentityGenerator.NewSequentialGuid(),
Amount = -amount,
ActivityDescription = reason
});
}
}
else
throw new
InvalidOperationException(Messages.exception_BankAccountCannotWithdraw);
}

public bool CanBeWithdrawed(decimal amount)
{
return !Locked && (this.Balance >= amount);
}

}


很多这些实体行为需要改变属性映射,因为任何属性都不是公共或者其他的属性必须是可读但不是直接更新的。

比如,我们可以扩展最初的实体来添加一些行为。因为OrderDetail是一个子实体,它不应该是直接访问的。另

一方面,所有对OrderDetail的操作都应该通过聚合根实体进行,例如下面:
//POCO Entity Class (Root Entity Class for Order-Aggregate)
public class Order : Entity, IValidatableObject
{

//.Id property is inherited from the Entity base Class
HashSet<OrderLine> _Lines;
public Guid CustomerId { get; set; }
public DateTime OrderDate { get; set; }
POCO Root Entity Class (For „Aggregate Order‟)
Business checks / validations
Business/Domain logic  for  the
BankAccount Withdraw process
Domain Entity logic
public DateTime? DeliveryDate { get; set; }
public virtual ShippingInfo ShippingInformation { get; set; }

public void AddOrderLine(OrderLine line)
{
if (line == null)
throw new ArgumentNullException("line");

//Fix relation
line.OrderId = this.Id;

this._Lines.Add(line);
}

//...
//More code omitted for brevity
//..
}

public class OrderLine : Entity, IValidatableObject
{
public decimal UnitPrice { get; set; }
public int Amount { get; set; }
public decimal Discount { get; set; }

public decimal TotalLine
{
get
{
return (UnitPrice * Amount) * (1 - Discount);
}
}

public Guid OrderId { get; set; }

}


顺便说一下,使用STE和POCO T4实体并不适合这样灵活的映射改变,隐藏属性等。使用STE可以在T4模板中完成,

但是很多情况并不灵活。这是另一个POCO-代码优先更适合领域驱动设计的理由。

3.7.- 实现领域实体的基类

推荐使用实体基类这样可以把实体类通用的功能放入。典型的,比较方法或其它和实体标识符相关的操作。

下面我们展示了一个使用POCO代码优先的”实体基类“:

// Entity Base Class
//
public abstract class Entity
{
int? _requestedHashCode;
public Guid Id { get; set; }

public bool IsTransient()
{
return this.Id == Guid.Empty;
}

public override bool Equals(object obj)
{
if (obj == null || !(obj is Entity))
return false;

Entity item = (Entity)obj;

if (item.IsTransient() || this.IsTransient())
return false;
else
return item.Id == this.Id;
}

public static bool operator ==(Entity left, Entity right)
{
return left.Equals(right);
}

public static bool operator !=(Entity left, Entity right)
{
return !left.Equals(right);
}

...
//Other methods
...
}


这样我们可以继承实体这样通用的实体行为可以重用。例如下面对BankAccount实体继承自实体基类:

//POCO Domain Entity implementing our Entity Base-Class
//
public class BankAccount : Entity,IValidatableObject
{
//Attributes
//
public string BankAccountNumber { get; set; }
public decimal Balance { get; set; }
public int CustomerId { get; set; }
public bool Locked { get; set; }
...
//

//Other specific Domain Entity Logic (Methods)
//
...
...
...
}


使用基类会非常有用尤其对于标识符的管理,比较实例。

3.8.- EF 4.1”代码优先“领域实体的协定(Conventions)

EF 4.1”代码优先“利用一个编程模式叫做配置上的协定(Conventions)。如果继续使用领域实体,使用一个

简单的EF DbContext,就可以运行你的代码。不需要创建数据库表或任何事先的映射。

// EF DbContext
// This code should be part of your Data Persistence Infrastructure Layer
public class MainBCUnitOfWork : DbContext, IMainBCUnitOfWork
{
//SIMPLIFIED...
public DbSet<Customer> Customers { get; set; }
public DbSet<BankAccount> BankAccount { get; set; }
}


DbContext是EF 4.1新出现的,可以通过”代码优先“使用或者也可以使用”模型/数据库优先“。

这就是我们开始存储和获取数据的全部代码。显然需要做更多的事,我们会在下面的章节看到。

同时,在我们的领域驱动架构里我们不使用简单的DbContext,会抽出“Unit of Work‟接口,使用依赖

注入等等。

这意味着代码优先会假定你的类按照默认的协定(Conventions)。这种情况,EF可以按照具体的细节

来做它的事。下面的代码可以执行。(这是一个孤立概念的证明,不是任何示例)。

// Using ‘Code First’
class Program
{
static void Main(string[] args)
{
using (var db = new MainBCUnitOfWork())
{
// Add a Customer
var customer = new Customer {CustomerId = "ALFKI", Name = "Joe Smith"};
db. Customers.Add(customer);
int recordsAffected = db.SaveChanges();

Console.WriteLine(
"Saved {0} entities to the database, press any key to exit.",
recordsAffected);

Console.ReadKey();
}
}
}


DbContext的协定(Conventions)会在localhost\SQLEXPRESS上创建数据库。数据库由你派生的context

命名。这和数据访问层有关,所以我们会在下面的章节看到如果改变命名。另外,协定(Conventions)对于

”发现模型“工作。DbContext通过DbSet的属性找到包含在模型中的类。然后使用默认的代码优先协定(Conventions)

来找到主键,外键等等。

例如,DbContext根据前缀带有"Id"的属性推断实体和表的主键。因此,对于BankAccount实体,按照协定

主键会是BankAccountId。如果想有多个或符合键,会有一些方法来做。

下面是一些有意思的默认协定:

表 13.- EF 4.1主要的 ‘默认协定’

协定 描述

IdKeyDiscoveryConvention 主键属性的协定

ComplexTypeDiscoveryConvention 如果没有主键,没有基本类型,没有导航属性时配置复杂类

ForeignKeyAssociationMultiplicityConvention 基于外键属性的是否为空区分可选和必须的关系

ManyToManyCascadeDeleteConvention 在多对多关系中假如级联删除

NavigationPropertyNameForeignKeyDiscoveryCon 发现名字是独立导航属性和主键的组合时的外键

vention

OneToManyCascadeDeleteConvention 关联的级联删除

OneToOneConstraintIntroductionConvention 在一对一关系中配置依赖实体类型的主键作为外键

PluralizingEntitySetNameConvention 设置实体集合的名字作为实体类型的复数。

PluralizingTableNameConvention 设置实体类型复数的表名

PrimaryKeyNameForeignKeyDiscoveryConvention 发现符合主键属性名称的外键属性

StoreGeneratedIdentityKeyConvention 配置整形的主键作为标识符

PropertyMaxLengthConvention 设置属性类型的最大长度

TypeNameForeignKeyDiscoveryConvention 发现名称是主要类型和主要类型主键属性组合的外键属性

还有很多其他的关于自定义模型的协定。

对于EF 4.1的完整列表和解释请看下面:
http://msdn.microsoft.com/en-us/library/system.data.entity.modelconfiguration.conventions(v=vs.103).aspx
在我们不要应用默认协定的情况,我们可以忽略它们,这是DbContext自定义的一部分,因此,我们会在

数据访问基础结构的章节解释。

然而,如果你的类不按照默认的协定,你可以在类上添加配置来匹配已有的数据库。这可以通过两种方法做到:

”数据注解“或”Fluent API“。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: