贫血域模型的处理技术

发布于 2024-07-14 15:40:56 字数 1480 浏览 8 评论 0原文

我读过一些有关贫血领域模型和关注点分离的问题。 在贫血域对象上执行/附加域逻辑的最佳技术是什么? 在我的工作中,我们有一个相当贫乏的模型,并且我们目前正在使用“帮助器”类在域对象上执行数据库/业务逻辑。 例如:

public class Customer
{
    public string Name {get;set;}
    public string Address {get;set;}
}

public class Product
{
    public string Name {get;set;}
    public decimal Price {get;set;}
}

public class StoreHelper
{
    public void PurchaseProduct(Customer c, Product p)
    {
         // Lookup Customer and Product in db
         // Create records for purchase
         // etc.
    }
}

当应用程序需要进行购买时,它会创建 StoreHelper,并调用域对象上的方法。 对我来说,客户/产品知道如何将自身保存到存储库是有意义的,但您可能不希望域对象上有 Save() 方法。 对于像 Customer.Purchase(Product) 这样的方法也有意义,但这是将域逻辑放在实体上。

以下是我遇到的一些技术,不确定哪些是好是坏:

  1. Customer 和 Product 继承自“Entity”类,该类以通用方式提供基本的 CRUD 操作(可能使用 ORM)。
    • 优点:每个数据对象都会自动获取 CRUD 操作,但随后会绑定到数据库/ORM
    • 缺点:这并不能解决对象上的业务操作问题,并且还将所有域对象绑定到可能不合适的基础实体
  2. 使用帮助器类来处理 CRUD 操作和业务逻辑
    • 使用 DAO 来执行“纯数据库”操作,并使用单独的业务助手来执行更具体的业务操作是否有意义?
    • 为此使用非静态还是静态辅助类更好?
    • 优点:域对象不依赖于任何数据库/业务逻辑(完全贫乏)
    • 缺点:不太面向对象,在应用程序代码中使用助手不太自然(看起来像 C 代码)
  3. 使用双分派技术,其中实体具有保存方法到任意存储库
    • 优点:更好地分离关注点
    • 缺点:实体附加了一些额外的逻辑(尽管它是解耦的)
    • 优点
  4. 在 C# 3.0 中,您可以使用扩展方法将 CRUD/业务方法附加到域对象,而无需触及它
    • 这是一种有效的方法吗? 有什么优点/缺点?
  5. 其他技术?

处理这个问题的最佳技术是什么? 我对 DDD 还很陌生(我正在读 Evans 的书 - 所以也许这会让我大开眼界)

I've read some of the questions regarding anemic domain models and separation of concerns. What are the best techniques for performing/attaching domain logic on anemic domain objects? At my job, we have a pretty anemic model, and we're currently using "helper" classes to perform the database/business logic on the domain objects. For example:

public class Customer
{
    public string Name {get;set;}
    public string Address {get;set;}
}

public class Product
{
    public string Name {get;set;}
    public decimal Price {get;set;}
}

public class StoreHelper
{
    public void PurchaseProduct(Customer c, Product p)
    {
         // Lookup Customer and Product in db
         // Create records for purchase
         // etc.
    }
}

When the app needs to do a purchase, it would create the StoreHelper, and call the method on the domain objects. To me, it would make sense for the Customer/Product to know how to save itself to a repository, but you probably wouldn't want Save() methods on the domain objects. It would also make sense for a method like Customer.Purchase(Product), but that is putting domain logic on the entity.

Here are some techniques I've come across, not sure which are good/bad:

  1. Customer and Product inherit from an "Entity" class, which provides the basic CRUD operations in a generic fashion (using an ORM maybe).
    • Pros: Each data object would automatically get the CRUD operations, but are then tied to the database/ORM
    • Cons: This does not solve the problem of business operations on the objects, and also ties all domain objects to a base Entity that might not be appropriate
  2. Use helper classes to handle the CRUD operations and business logic
    • Does it make sense to have DAOs for the "pure database" operations, and separate business helpers for the more business-specific operations?
    • Is it better to use non-static or static helper classes for this?
    • Pros: domain objects are not tied to any database/business logic (completely anemic)
    • Cons: not very OO, not very natural to use helpers in application code (looks like C code)
  3. Use the Double Dispatch technique where the entity has methods to save to an arbitrary repository
    • Pros: better separation of concerns
    • Cons: entities have some extra logic attached (although it's decoupled)
  4. In C# 3.0, you could use extension methods to attach the CRUD/business methods to a domain object without touching it
    • Is this a valid approach? What are pros/cons?
  5. Other techniques?

What are the best techniques for handling this? I'm pretty new to DDD (I'm reading the Evans book - so maybe that will open my eyes)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

眼眸里的快感 2024-07-21 15:40:56

为了避免贫血模型,请重构您的辅助类:

逻辑如下:
"Customer.PurchaseProduct(Product 产品, Payment 付款)",
“Customer.KillCustomer(杀人者,武器武器)”
应该存在于“客户”域对象中。

逻辑如下:
“Customer.IsCustomerAlive()”
“Customer.IsCustomerHappy()”
应该去规范。

逻辑如下:
“客户.Create()”,
“客户.Update()”
显然应该去存储库。

逻辑如下:
“Customer.SerializeInXml()”
“Customer.GetSerializedCustomerSizeInBytes()”
应该去服务。

复杂的构造函数应该去工厂。

我就是这么看的。 如果有人能评论我对 DDD 方法的理解,我会很高兴。


编辑:

有时 - 贫血域模型不应避免

编辑了我的答案,添加 DDD 不是关于拾取和丢弃模式。
DDD 是关于我们的思维方式。

In order to avoid anemic model, refactor your helper classes:

Logic like:
"Customer.PurchaseProduct(Product product, Payment payment)",
"Customer.KillCustomer(Person killer, Weapon weapon)"
should exist right into "Customer" domain object.

Logic like:
"Customer.IsCustomerAlive()"
"Customer.IsCustomerHappy()"
should go to specifications.

Logic like:
"Customer.Create()",
"Customer.Update()"
obviously should go to repositories.

Logic like:
"Customer.SerializeInXml()"
"Customer.GetSerializedCustomerSizeInBytes()"
should go to services.

Complex constructors should go to factories.

That's how i see it. I would be glad if someone could comment my understanding of DDD approach.


Edit:

Sometimes - anemic domain model shouldn't be avoided.

Edited my answer to add that DDD isn't about picking up and dropping patterns.
DDD is about way we think.

匿名。 2024-07-21 15:40:56

Martin Fowler 写了很多关于领域模型的文章,包括贫血领域模型。 他还对领域模型和数据库的许多设计模式进行了简要描述(和 UML 类图),这些设计模式可能会有所帮助:Catalog of "企业应用架构模式”

我建议查看 Active Record数据映射器 模式。 从您的问题描述来看,您的帮助程序类似乎同时包含域/业务规则和数据库实现详细信息。

Active Record 会将帮助程序的域逻辑和数据库代码移至其他域对象(例如您的Entity 基类)。 数据映射器会将帮助器的域逻辑移至域对象中,并将数据库代码移至单独的映射对象中。 任何一种方法都比过程式帮助程序类更加面向对象。

Eric Evans 的《领域驱动设计》一书非常棒。 虽然有点干,但绝对值得。 InfoQ 有一本“快速领域驱动设计”迷你书,它是一本对埃文斯这本书的很好的介绍。 另外,“快速领域驱动设计”以免费 PDF 形式提供。

Martin Fowler has written a lot about domain models, including anemic domain models. He also has brief descriptions (and UML class diagrams) of many design patterns for domain models and databases that might be helpful: Catalog of "Patterns of Enterprise Application Architecture".

I would suggest looking at the Active Record and Data Mapper patterns. From the description of your problem, it sounds like your helper classes contain both domain/business rules and database implementation details.

The Active Record would move the helper's domain logic and database code into the other domain objects (like your Entity base class). The Data Mapper would move the helper's domain logic into the domain objects and the database code into a separate map object. Either approach would be more object-oriented than procedural-style helper classes.

Eric Evans' "Domain Driven Design" book is excellent. It gets a bit dry, but is definitely worth it. InfoQ has a "Domain Driven Design Quickly" mini-book that is a good introduction to Evans' book. Plus "Domain Driven Design Quickly" is available as a free PDF.

平安喜乐 2024-07-21 15:40:56

我一直认为贫血域模型是一种反模式。 很明显,客户将购买产品,这种能力可以通过接口实现来泛化

Interface IPurchase
      Purchase(Product);

,因此您的任何域对象都可以根据需要实现它。 通过这种方式,您可以向域对象引入功能 - 这正是它应该在的位置。

I've always thought of the anemic domain model as an anti pattern. It's clear that a customer will purchase products, that ability can be generised by an interface implementation

Interface IPurchase
      Purchase(Product);

, so a any of your domain objects can then implement that as required. In that way you can introduce functionality to your domain objects - which is exactly where it should be.

熟人话多 2024-07-21 15:40:56

您没有提到的一种方法是使用 AOP 来处理数据访问。 我最近使用这种方法的一个例子(尽管为了发布目的而大大简化)是我有一个 Account 域实体,它有一个 debit 方法,封装了所需的业务逻辑以便成功从账户中扣款。

NB 所有代码都是带有 AspectJ AOP 表示法的 Java...

public boolean debit(int amount) {
    if (balance - amount >= 0) {
        balance = balance - amount;
        return true;
    }
    return false;
}

将适当的存储库注入到我的方面中,然后我使用切入点来拦截对此方法的调用...

pointcut debit(Account account,int amount) :
    execution(boolean Account.debit(int)) &&
    args(amount) &&
    target(account);

...并应用了一些建议:

after(Account account, int amount) returning (boolean result)  : debit(account,amount) {
    if (result) getAccountRepository().debit(account, amount);
}

在我看来,这提供了很好的关注点分离,并允许您的域实体完全专注于应用程序的业务逻辑。

One approach that you haven't mentioned is using AOP to handle your data access. An example of my recent use of this approach (though vastly simplified for posting purposes) was that I had an Account domain entity which had a debit method, encapsulating the business logic required in order to make a successful debit from the account.

N.B. All code is Java with AspectJ AOP notation...

public boolean debit(int amount) {
    if (balance - amount >= 0) {
        balance = balance - amount;
        return true;
    }
    return false;
}

With the appropriate repository injected into my aspect, I then used a pointcut to intercept calls to this method...

pointcut debit(Account account,int amount) :
    execution(boolean Account.debit(int)) &&
    args(amount) &&
    target(account);

...and applied some advice:

after(Account account, int amount) returning (boolean result)  : debit(account,amount) {
    if (result) getAccountRepository().debit(account, amount);
}

In my opinion this gives a nice separation of concerns, and allows your domain entities to focus entirely on the business logic of your application.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文