RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 670236
Accepted
coder
coder
Asked:2020-05-24 20:24:19 +0000 UTC2020-05-24 20:24:19 +0000 UTC 2020-05-24 20:24:19 +0000 UTC

DDD, Aggregate root without ORM, 如何保存?

  • 772
public class Order
{
   List<OrderItem> Items {get; private set;}

   public AddItem(OrderItem item)
   {
       //логика добавления

       items.Add(item);
   }
}

遵循 DDD 方法论,所有领域逻辑都位于领域内部,不会被取出到单独的服务中。问题是如何在不使用 ORM或使用 microOrm - Dapper 的情况下保存对聚合根的更改。你好吗?

java
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    Mark Shevchenko
    2020-05-30T16:33:12Z2020-05-30T16:33:12Z

    按照 DDD 的原则,storages 负责存储实体,它们也是允许你读写聚合的存储库。在这种情况下,订单存储库必须能够读取和写入订单聚合,其中包括订单项。

    由于实体的存储方式很可能会发生变化,因此我们在域中声明的不是存储库的实现,而是接口:

    public interface IOrderRepository
    {
        Order Create();
    
        Order ReadById(int id);
    
        void Update(Order order);
    
        void DeleteById(ind id);
    }
    

    该接口的实现将驻留在应用程序的基础设施层(Eric Evans 的术语)。不是DDD,这一层叫做数据访问层。按照数据隐藏的原则,订单实体的内部数据是无法从基础设施层到达的。

    示例:一个实体Order有一个属性CreatedAt,即创建日期。根据域的规则,这个属性是只读的,即有getter方法,没有setter方法。当订单存储库从数据库加载数据时,它必须设置所有属性的值,包括CreatedAt. 但它不能那样做,因为它不能改变只读属性的值。

    GoF 中描述了类似的问题,其中使用Memento ( Keeper )模式来解决它。它允许您保存对象的状态并在以后恢复它。在经典方案中,状态以无法分析和理解的形式存储,但我们需要其他东西。

    如果我们使用 DBMS,我们希望状态采用一种便于存储在 DBMS 中的形式。为此,我们为实体引入了 DTO。这些对象也位于域层中,因为它们是基础设施层接口的一部分。

    public class OrderDto
    {
        public int Id { get; set; }
    
        public DateTime CreatedAt { get; set; }
    
        public OrderItemDto[] Items { get; set; }
    }
    
    public class OrderItemDto
    {
        public int Id { get; set; }
    
        public int ProductId { get; set; }
    
        public decimal Count { get; set; }
    
        public decimal Amount { get; set; }
    }
    
    public class Order
    {
         private readonly OrderDto dto;
         private readonly List<OrderItem> items;
    
         public int Id { get { return dto.Id; } }
    
         public DateTime CreatedAt { get { return dto.CreatedAt; } }
    
         public IReadOnlyCollection<OrderItem> Items { . . . }
    
         internal Order(OrderDto dto)
         {
             this.dto = dto;
    
             items = new List<OrderItem>();
             foreach (var orderItemDto in dto.Items)
                 items.Add(new OrderItem(orderItemDto));
         }
    
         public void AddItem(Product product, decimal count)
         {
             var itemDto = new OrderItemDto
             {
                 Id = 0,
                 OrderId = this.Id,
                 ProductId = product.Id,
                 Count = count,
                 Amount = product.Price * count
             };
    
             dto.Items.Add(itemDto);
    
             var item = new OrderItem(itemDto);
             items.Add(item);
         }
    }
    
    public class OrderItem
    {
        internal OrderItem(OrderItemDto dto)
        {
            . . .
        }
    
        . . .
    }
    

    在这种形式中,存储库从数据库加载数据,将其转换为 DTO 对象,然后从 DTO 对象创建域对象。要么获取域实体,将其转换为 DTO 对象,然后保存。

    public class AdoOrderRepository : IOrderRepository
    {
        private readonly SqlConnection connection;
    
        public AdoOrderRepository(SqlConnection connection)
        {
            this.connection = connection;
        }
    
        public Order ReadById(int id)
        {
            using (var command = connection.CreateCommand())
            {
                // загружаем данные заказа включая агрегированные позиции
                // в объект OrderDto
                OrderDto orderDto = . . .;
    
                // волшебным образом преобразуем OrderDto в Order
                Order order = . . .;
    
                return order;
            }
        }
    
        public void Update(Order order)
        {
            using (var command = connection.CreateCommand())
            {
                // волшебным образом преобразуем Order в OrderDto
                OrderDto orderDto = . . .;
    
                // обновляем данные из OrderDto
                . . .
            }
        }
    }
    

    让我再修正一下:领域层描述了领域实体(Order, ),它分别描述了需要在基础设施层( )中实现OrderItem的存储库接口( ) ,最后,它描述了 DTO 对象(, )包含实体 ( ) 想要永久存储的所有字段。实体 ( ) 包含逻辑并隐藏实现,DTO 仅包含数据而没有逻辑。IOrderRepositoryAdoOrderRepositoryOrderDtoOrderItemDtoOrderOrder

    存储库实现能够与 DTO 对象一起工作,特别是,它们可以向数据库发送读取请求并将结果写入 DTO 对象,因为 DTO 对象非常简单。但是他们如何将 DTO 对象转换为实体,反之亦然?在我上面提供的代码示例中,我写到这神奇地发生了。

    现在是弄清楚具体方法的时候了。域本身的对象,例如Order. 但它们有一个主要功能 - 这些是主题区域的订单。添加第二个功能将违反单一职责原则。我们需要一个单独的类来访问实体状态Order。但是一个类不需要知道另一个类的实现细节。

    嵌套此类时除外。

    public class Order
    {
        private readonly OrderDto dto;
    
        private Order(OrderDto dto)
        {
            this.dto = dto;
        }
    
        . . .
    
        public static class DtoMapper
        {
            public static void Map(Order order, OrderDto orderDto)
            {
                . . .
            }
    
            public static void Map(OrderDto orderDto, Order order)
            {
                . . .
            }
        }
    }
    

    内部类被描述为公共的,因此基础设施级别的存储库实现可以访问它。

    使用这种方法,存储库的实现方式无关紧要:通过像实体框架这样的大型 ORM,通过简单的 microORM Dapper,或通过 ADO。

    您需要小心聚合对象,例如 collections OrderItem/ OrderItemDto。Inside 我们有一个 collection OrderItemDto, outside OrderItem,它们必须匹配。这个任务不是很困难。

    最后,我想说明域对象和 DTO 之间的区别,因为有时看起来它们之间有太多共同点,以至于可以放弃 DTO。有时确实有很多共同点,但有时却没有。

    如果我们有一个有密码的用户实体,那么在主题区域,它看起来像这样:

    public class User
    {
        public int Id { get; }
    
        public DateTime CreatedAt { get; }
    
        public string Login { get; }
    
        bool ValidatePassword(string password);
    
        void ChangePassword(string oldPassword, string newPassword);
    }
    

    DTO 对象因为它看起来完全不同。

    public class UserDto
    {
        public int Id { get; set; }
    
        public DateTime CreatedAt { get; set; }
    
        public string Login { get; set; }
    
        public byte[] PasswordHash { get; set; }
    }
    

    如您所见,DTO 对象提供对原始数据的访问,在本例中为密码哈希。域对象隐藏哈希,不仅用于写入,而且用于读取,以避免可能的安全问题。它提供了告诉我们如何使用对象ValidatePassword的方法。ChangePasswordUser

    这就是为什么 DTO 对象和主题领域的实体,尽管有一些重复的领域,但属于不同的层次并具有不同的目的。

    有一些关于如何设计 DTO 的技巧。理想情况下,它们应该被制作成可以立即在实体框架、NHibernate 或 Dapper 中使用。这意味着您可以使用名称空间中的外键、导航属性和类似属性。这不是必需的,但会使存储库的实现更容易。另一方面,最好不要依赖 ORM 特定的属性和解决方案,例如. 通过这种方式,您可以让 DTO 与特定的 ORM 分离,并可以快速从 EF/SQL 迁移到 MongoDB 或 Redis。[Key][TableName]System.Components.Annotations[Index]

    最后,经过无尽的理论,我将对这个问题给出一个具体的答案——在不使用ORM的情况下,究竟如何保存对聚合根的更改。我将添加方法AdoOrderRepository.Update:

    public void Update(Order order)
    {
        var orderDto = new OrderDto();
        Order.DtoMapper.Map(order, orderDto);
    
        using (var transaction = connection.BeginTransaction())
        {
            var orderDto = new OrderDto();
            Order.DtoMapper.Map(order, orderDto);
    
            var command = connection.CreateCommand();
            command.CommandText = "UPDATE [Orders] SET CreatedAt = @CreatedAt WHERE Id = @Id";
            // Поле CreatedAt только для чтения, так что я просто
            // иллюстрирую идею.
            command.Parameters.Add("@CreatedAt", orderDto.CreatedAt);
            command.Parameters.Add("@Id", orderDto.Id);
    
            command.ExecuteNonQuery();
    
            command.CommandText = "UPDATE [OrderItems] SET Count = @Count, Amount = @Amount WHERE Id = @Id";
            foreach (var item in dto.Items.Where(x => x.Id != 0))
            {
                command.Parameters.Clear();
                command.Parameters.Add("@Count", item.Count);
                command.Parameters.Add("@Amount", item.Amount);
                command.Parameters.Add("@Id", item.Id);
    
                command.ExecuteNonQuery();
            }
    
            command.CommandText = "INSERT [OrderItems] (OrderId, ProductId, Count, Amount) VALUES (@OrderId, @ProductId, @Count, @Amount)";
            foreach (var item in dto.Items.Where(x => x.Id == 0))
            {
                command.Parameters.Clear();
                command.Parameters.Add("@OrderId", item.OrderId);
                command.Parameters.Add("@ProductId", item.ProductId);
                command.Parameters.Add("@Count", item.Count);
                command.Parameters.Add("@Amount", item.Amount);
    
                item.Id = (int)command.ExecuteScalar();
            }
    
            transaction.Commit();
        }
    
        Order.DtoMapper.Map(orderDto, order);
    }
    

    按照 DDD,我们不仅要保存聚合的根,还要保存所有聚合的实体,这就是我们在这个方法中所做的。我们创建一个事务来保证变化的一致性。代码很繁琐,但很简单。为了摆脱笨重,你可以使用 Dapper。

    • 13

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5