【问题标题】:How to Add Business Logic to the Domain Service in Domain-Driven-Design?如何在领域驱动设计中将业务逻辑添加到领域服务中?
【发布时间】:2012-08-16 05:21:47
【问题描述】:

在我的项目中,业务逻辑都在应用程序服务中,域服务只是一些实体,谁能告诉我或给我一个例子来展示如何在域驱动设计中将业务逻辑添加到域服务中?非常感谢!

更新

我写了一个简单的解决方案,这个解决方案是一个投票系统,解决方案的主要部分是:

Vote.Application.Service.VoteService.cs:

namespace Vote.Application.Service
{
    public class VoteService
    {
        private IVoteRepository _voteRepository;
        private IArticleRepository _articleRepository;

        public VoteService(IVoteRepository voteRepository,IArticleRepository articleRepository)
        {
            _voteRepository = voteRepository;
            _articleRepository = articleRepository;
        }

        public bool AddVote(int articleId, string ip)
        {
            var article = _articleRepository.Single(articleId);
            if (article == null)
            {
                throw new Exception("this article not exist!");
            }
            else
            {
                article.VoteCount++;
            }

            if (IsRepeat(ip, articleId))
                return false;

            if (IsOvertakeTodayVoteCountLimit(ip))
                return false;

            _voteRepository.Add(new VoteRecord()
            {
                ArticleID = articleId,
                IP = ip,
                VoteTime = DateTime.Now
            });

            try
            {
                _voteRepository.UnitOfWork.Commit();
                return true;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private bool IsRepeat(string ip, int articleId)
        {
            //An IP per article up to cast 1 votes
            //todo
            return false;
        }

        private bool IsOvertakeTodayVoteCountLimit(string ip)
        {
            //An IP per day up to cast 10 votes
            //todo
            return false;
        }
    }
}

Vote.Domain.Contract.IVoteRepository.cs:

namespace Vote.Domain.Contract
{
    public interface IVoteRepository
        : IRepository<VoteRecord>
    {
        void Add(VoteRecord model);
    }
}

Vote.Domain.Contract.IArticleRepository.cs:

namespace Vote.Domain.Contract
{
    public interface IArticleRepository
        : IRepository<Article>
    {
        void Add(VoteRecord model);

        Article Single(int articleId);
    }
}

Vote.Domain.Entities.VoteRecord:

namespace Vote.Domain.Entities
{
    public class VoteRecord
    {
        public int ID { get; set; }

        public DateTime VoteTime { get; set; }

        public int ArticleID { get; set; }

        public string IP { get; set; }
    }
}

Vote.Domain.Entities.Article:

namespace Vote.Domain.Entities
{
    public class Article
    {
        public int ID { get; set; }

        public string Title { get; set; }

        public string Content { get; set; }

        public int VoteCount { get; set; }
    }
}

我想把application.service中的Business Login移到Domain.service(目前不是这个项目),谁能帮帮我?怎么做才合理?非常感谢!

【问题讨论】:

  • 你能提供一些你的领域对象的例子吗?
  • @casablanca 我更新了我的问题
  • IP是什么意思?文章和投票记录之间有什么关系吗?请发布类文章
  • @CuongLe 感谢评论,我已经更新了我的问题。
  • 我仍然很想将域服务用于您的应用程序服务所做的很多事情。根据我对 DDD 应用程序服务的了解(根据我在书中看到的内容!)倾向于仅使用请求/响应 dto 对象来路由请求。

标签: c# architecture domain-driven-design


【解决方案1】:

DDD 关注的是如何设计符合需求的领域模型,数据库中的模式并不重要。

如果您的域实体只是属性包,那么您似乎违反了Anemic Model anti-pattern。业务逻辑应该在域实体中。因此,在您的情况下,为了避免业务逻辑泄漏到应用程序服务。如有必要,您可以使用一个名为 Client 的新模型来存储 Ip 或其他属性。

为了便于理解,Client 是否超过了一天的限制,这个方法应该在 Client 类中。与 IsRepeated 方法类似。

所以,你的域对象应该是:

public class Client
{
    public string Ip { get; set; }
    // More properties if needed

    public List<Vote> Votes { get; set; }

    public bool ExceedLimitInDay()
    {
    }
}

public class Vote
{ 
    public int Id { get; set; } 
    public DateTime VoteTime { get; set; } 
    public Article Article { get; set; } 
    public Client { get; set; } 
}

public class Article   
{   
    public int Id { get; set; }   
    public string Title { get; set; }   
    public string Content { get; set; }   

    public List<Vote> Votes { get; set; }

    public bool IsRepeated(string ip)
    {
        return Votes.Select(v => v.Client.Ip == ip).Any();      
    }
} 

注意:如果您不需要创建新表Client,只需将其映射到Vote表即可。至于 VoteCount 属性,则不需要,因为您可以根据 Vote 列表计数

【讨论】:

  • +1 将投票列表放在文章中是正确的方法。我会避免对投票中的文章进行反向引用,因为它会创建循环引用——如有必要,可以通过存储库进行查找,例如。 Article article = repository.FindArticleByVote(vote).
【解决方案2】:

我认为 Vote 和 Article 是一个聚合体。 我对您的域名了解不多,但我认为每次投票只对给定的文章很重要。投票不会在文章之间共享,因此它们应被视为聚合的成员,而不是整个域的实体。

在我看来,Article 将是 Aggregate 的根实体,而 Vote 将是内部的成员实体。 应用这种“模式”,您可以将所有业务逻辑封装在其中。

您可以在文章中添加 AddVote(...) 或类似的一些业务逻辑。

那么您可能想知道..“好的.. 但是持久性呢??”。 由于您将 Article-Vote 定义为聚合,因此您可以将它们放在一起处理。如果您对文章执行 CRUD 操作,同时您将在其聚合成员(投票)中执行这些操作。所以你可能需要一个 ArticleAggregate 的存储库,它会处理文章的投票。

希望这对您有意义。根据您的域选择最佳选项。

【讨论】:

    【解决方案3】:

    从一瞥应用程序服务我会说它几乎不是应用程序服务,它已经有点像域服务了,因为它捕获了一些域逻辑(而不是获取视图模型输入并将其映射到实体上)。但是,您确实有更大的问题:

    您的聚合已关闭。您希望围绕一个 IP 地址强制执行两个不变量,对一篇文章进行投票(一个 ip 每篇文章只能投票一次,一个 ip 每天最多可以投票 10 次)。这两个名词都不适合(文章、投票)跟踪。

    ArticleVotes 可能非常适合跟踪和强制执行单个 ip 对文章的投票,而 IPVotesPerDate 可能会跟踪单个 IP 地址每天的投票数。如果总票数足够低,您也许可以将 ArticleVotes 移动到 Article 聚合中(并且您可以将性能保持在您想要的位置)。如果每天投票的 IP 总数较低,则 IPVotesPerDate 可能会被转换为 VotesPerDate。

    底线是 ip 和投票量将决定聚合的性能。这可能会迫使您对它们进行改造以适应您寻求的性能数字。最好的办法是找到这些数字,以了解前进的方向。

    从那里应该很明显可以将所需的行为带入我提到的聚合中。因此,对域服务的需求可能会消失。我说可能,因为用例仍然是对文章投票的 ip。因此,可能需要跨聚合进行一些协调,因此需要引入域服务。

    【讨论】:

    • 如果ips和投票数很高。你将如何设计它?
    • 我认为不变量是错误的,只要接受所有写入并在事后进行重复数据删除。
    • 重复数据删除是什么意思?如何创建一个像 VotingService 这样的域服务,您可以在其中检查不变量,然后再接受投票?其实就是什么OP代码,移到领域层就好了。
    • 我的意思是我只会计算给定 ip 的最大票数。任何后续写入都将从计算中丢弃。计算是在事后进行的,而不是在 ip 投票时进行。
    • 域服务不会解决事务性方面的 TBH。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多