一笔交易中的多个聚合/存储库

2024-01-01

我有一个支付系统,如下所示。可以通过多张礼券进行支付。礼券随购买一起发放。客户可以使用此礼券进行日后购买。

当通过礼券进行付款时,GiftCoupon 表中的UsedForPaymentID 列需要使用该PaymentID 进行更新(对于礼券ID)。

GiftCouponID 已在数据库中可用。当顾客出示礼券时,上面会印有GiftCouponID。操作员需要将此CouponID输入到系统中进行支付。

对于 MakePayment() 操作,它需要两个存储库。

  1. 礼券库
  2. 支付库

CODE

//使用GiftCouponRepository检索对应的GiftCoupon对象。

这涉及一笔交易使用两个存储库。这是一个好的做法吗?如果不是,我们如何改变设计来克服这个问题?

参考:在 DDD 中,聚合应该代表事务边界。需要涉及多个聚合的事务通常表明应该改进模型,或者应该审查事务要求,或者两者兼而有之。CQRS 对于我的域是否正确? https://stackoverflow.com/questions/11169097/is-cqrs-correct-for-my-domain

C# CODE

public RepositoryLayer.ILijosPaymentRepository repository { get; set; }

public void MakePayment(int giftCouponID)
{
    DBML_Project.Payment paymentEntity = new DBML_Project.Payment();
    paymentEntity.PaymentID = 1;

    DBML_Project.GiftCoupon giftCouponObj;

    //Use GiftCouponRepository to retrieve the corresponding GiftCoupon object.     

    paymentEntity.GiftCouponPayments = new System.Data.Linq.EntitySet<DBML_Project.GiftCoupon>();
    paymentEntity.GiftCouponPayments.Add(giftCouponObj);

    repository.InsertEntity(paymentEntity);
    repository.SubmitChanges();
}

我认为你真正想问的是关于'一笔交易中的多个聚合'。我不认为使用多个存储库有什么问题获取事务中的数据。通常在事务期间,聚合需要来自其他聚合的信息,以便决定是否或如何更改状态。没关系。然而,在一个事务中修改多个聚合的状态被认为是不可取的,我认为这就是您引用的引用试图暗示的内容。

这是不受欢迎的原因是并发性。除了保护其边界内的不变量之外,还应该保护每个聚合免受并发事务的影响。例如两个用户同时对聚合进行更改。

这种保护通常是通过在聚合的数据库表上拥有版本/时间戳来实现的。保存聚合时,将对正在保存的版本和当前存储在数据库中的版本进行比较(现在可能与事务开始时不同)。如果它们不匹配,则会引发异常。

基本上可以归结为:在协作系统(许多用户进行许多事务)中,单个事务中修改的聚合越多,将导致并发异常的增加。

如果您的聚合太大并且提供了许多状态更改方法,则情况完全相同;多个用户一次只能修改一个聚合。通过设计在事务中单独修改的小型聚合,可以减少并发冲突。

沃恩·弗农 (Vaughn Vernon) 在他的三部分文章中出色地解释了这一点。 http://dddcommunity.org/library/vernon_2011

然而,这只是一个指导原则,存在需要修改多个聚合的例外情况。事实上,您正在考虑是否可以重构事务/用例以仅修改一个聚合,这是一件好事。

考虑了您的示例后,我想不出一种将其设计为满足事务/用例要求的单个聚合的方法。需要创建付款,并且需要更新优惠券以表明其不再有效。

但是当真正分析潜在的并发问题时this交易,我认为礼券总量实际上不会发生冲突。它们仅被创建(发行)然后用于付款。其间没有其他状态改变操作。因此,在这种情况下,我们不需要担心我们正在修改付款/订单和礼品券聚合的事实。

下面是我很快想出的一种可能的建模方法

  • 如果没有付款所属的订单聚合,我无法理解付款如何有意义,因此我引入了一个。
  • 订单由付款组成。可以使用礼券付款。您可以创建其他类型的付款方式,例如现金付款或信用卡付款。
  • 要进行礼品券支付,必须将优惠券聚合传递到订单聚合。然后,这会将优惠券标记为已使用。
  • 交易结束时,订单总额将与其新付款一起保存,并且所有使用的礼券也会保存。

Code:

public class PaymentApplicationService
{
    public void PayForOrderWithGiftCoupons(PayForOrderWithGiftCouponsCommand command)
    {
        using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create())
        {
            Order order = _orderRepository.GetById(command.OrderId);

            List<GiftCoupon> coupons = new List<GiftCoupon>();

            foreach(Guid couponId in command.CouponIds)
                coupons.Add(_giftCouponRepository.GetById(couponId));

            order.MakePaymentWithGiftCoupons(coupons);

            _orderRepository.Save(order);

            foreach(GiftCoupon coupon in coupons)
                _giftCouponRepository.Save(coupon);
        }
    }
}

public class Order : IAggregateRoot
{
    private readonly Guid _orderId;
    private readonly List<Payment> _payments = new List<Payment>();

    public Guid OrderId 
    {
        get { return _orderId;}
    }

    public void MakePaymentWithGiftCoupons(List<GiftCoupon> coupons)
    {
        foreach(GiftCoupon coupon in coupons)
        {
            if (!coupon.IsValid)
                throw new Exception("Coupon is no longer valid");

            coupon.UseForPaymentOnOrder(this);
            _payments.Add(new GiftCouponPayment(Guid.NewGuid(), DateTime.Now, coupon));
        }
    }
}

public abstract class Payment : IEntity
{
    private readonly Guid _paymentId;
    private readonly DateTime _paymentDate;

    public Guid PaymentId { get { return _paymentId; } }

    public DateTime PaymentDate { get { return _paymentDate; } }

    public abstract decimal Amount { get; }

    public Payment(Guid paymentId, DateTime paymentDate)
    {
        _paymentId = paymentId;
        _paymentDate = paymentDate;
    }
}

public class GiftCouponPayment : Payment
{
    private readonly Guid _couponId;
    private readonly decimal _amount;

    public override decimal  Amount
    {
        get { return _amount; }
    }

    public GiftCouponPayment(Guid paymentId, DateTime paymentDate, GiftCoupon coupon)
        : base(paymentId, paymentDate)
    {
        if (!coupon.IsValid)
            throw new Exception("Coupon is no longer valid");

        _couponId = coupon.GiftCouponId;
        _amount = coupon.Value;
    }
}

public class GiftCoupon : IAggregateRoot
{
    private Guid _giftCouponId;
    private decimal _value;
    private DateTime _issuedDate;
    private Guid _orderIdUsedFor;
    private DateTime _usedDate;

    public Guid GiftCouponId
    {
        get { return _giftCouponId; }
    }

    public decimal Value
    {
        get { return _value; }
    }

    public DateTime IssuedDate
    {
        get { return _issuedDate; }
    }

    public bool IsValid
    {
        get { return (_usedDate == default(DateTime)); }
    }

    public void UseForPaymentOnOrder(Order order)
    {
        _usedDate = DateTime.Now;
        _orderIdUsedFor = order.OrderId;
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一笔交易中的多个聚合/存储库 的相关文章

  • 在 HKCR 中创建新密钥有效,但不起作用

    我有以下代码 它返回 成功 但使用两种不同的工具使用搜索字符串 3BDAAC43 E734 11D5 93AF 00105A990292 搜索注册表不会产生任何结果 RegistryKey RK Registry ClassesRoot C
  • 如何在类文件中使用 Url.Action() ?

    如何在 MVC 项目的类文件中使用 Url Action Like namespace 3harf public class myFunction public static void CheckUserAdminPanelPermissi
  • java中如何重新初始化int数组

    class PassingRefByVal static void Change int pArray pArray 0 888 This change affects the original element pArray new int
  • 强制初始化模板类的静态数据成员

    关于模板类的静态数据成员未初始化存在一些问题 不幸的是 这些都没有能够帮助我解决我的具体问题的答案 我有一个模板类 它有一个静态数据成员 必须为特定类型显式实例化 即必须专门化 如果不是这种情况 使用不同的模板函数应该会导致链接器错误 这是
  • RestSharp获取序列化输出

    我正在寻找一种方法来访问 AddBody 调用的序列化结果 我正在使用内置的 RestSharp 序列化器 例子 class Foo public string FooField void SendRecord var f new Foo
  • 不同 C++ 文件中的相同类名

    如果两个 C 文件具有相同名称的类的不同定义 那么当它们被编译和链接时 即使没有警告也会抛出一些东西 例如 a cc class Student public std string foo return A void foo a Stude
  • 如何使用 ASP.NET Core 获取其他用户的声明

    我仍在学习 ASP NET Core 的身份 我正在进行基于声明的令牌授权 大多数示例都是关于 当前 登录用户的 就我而言 我的 RPC 服务正在接收身份数据库中某个用户的用户名和密码 我需要 验证是否存在具有此类凭据的用户 获取该用户的所
  • 已发布的 .Net Core 应用程序警告安装 .Net Core,但它已安装

    我制作了一个 WPF 和控制台应用程序 供某人在我无法访问的私人服务器上使用 我使用 Visual Studio 2019 的内置 发布向导 来创建依赖于框架的单文件应用程序 当该人打开 WPF 应用程序时 他们会看到标准警告 他们单击 是
  • 模板外部链接?谁能解释一下吗?

    模板名称具有链接 3 5 非成员函数模板可以有内部链接 任何其他模板名称应具有外部链接 从具有内部链接的模板生成的实体与在其他翻译单元中生成的所有实体不同 我知道使用关键字的外部链接 extern C EX extern C templat
  • 在 C# 中为父窗体中的子窗体控件添加事件处理程序

    我有两种形式 一种是带有按钮和文本框的父表单 单击该按钮时 将打开一个对话框 该子窗体又包含一个文本框和一个按钮 现在我想要的是 每当子表单文本框中的文本更改时 父表单文本框中的文本会自动更改 为了获得这个 我所做的是 Form3 f3 n
  • C++ - 多维数组

    处理多维数组时 是否可以为数组分配两种不同的变量类型 例如你有数组int example i j 有可能吗i and j是两种完全不同的变量类型 例如 int 和 string 听起来您正在寻找 std vector
  • 将 Word 转换为 PDF - 禁用“保存”对话框

    我有一个用 C 编写的 Word 到 PDF 转换器 除了一件事之外 它工作得很好 有时 在某些 Word 文件上 后台会出现一条消息保存源文件中的更改 gt 是 否 取消 但我没有对源文件进行任何更改 我只想从 Word 文件创建 PDF
  • 模板类的模板构造函数的 C++ 显式模板特化

    我有一个像这样的课程 template
  • 使动态创建的链接标签在 Winforms 中可点击

    我正在制作一个程序 允许用户单击由动态链接标签创建的公司名称 在我想知道如何做到这一点之前 我从未在 C 中使用过链接标签 可为特定用户生成的业务数量各不相同 因此每个用户的链接标签数量并不相同 然后我想捕获业务 ID 以进行 Json 调
  • Visual Studio 2015:v120 与 v140?

    仅供参考 Win10 x64 我今天开始尝试 Visual Studio 2015 在弄清楚如何运行 C C 部分后 我尝试加载一个大型个人项目 该项目使用非官方的glsdk http glsdk sourceforge net docs
  • Visual Studio 2015 - Web 项目上缺少共享项目参考选项卡

    我从 MSDN 订阅升级到 Visual Studio 2015 因为我非常兴奋地阅读有关共享项目的信息 当我们想要做的只是重用代码时 不再需要在依赖项中管理 21382 个 nuget 包 所以我构建了一个测试共享项目 其中包含一些代码
  • 了解 Lambda 表达式和委托 [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我已经尝试解决这个问题很长一段时间了 阅读在线博客和文章 但到目前为止还没有成功 什么是代表 什么是 Lambda 表达式 两者的优点
  • 没有“对 *this”功能的右值引用的解决方法

    我有一个围绕可移动对象的代理容器类 并希望代理能够隐式生成对底层对象的右值引用 但仅当代理本身被移动时 我相信我将能够按照提案 n2439 实施此行为 将移动语义扩展到 this http www open std org jtc1 sc2
  • 在 System.Type 上使用条件断点时出错

    这是函数 public void Init System Type Type this Type Type BuildFieldAttributes BuildDataColumns FieldAttributes 我在第一行设置了一个断点
  • MySqlConnectionStringBuilder - 使用证书连接

    我正在尝试连接到 Google Cloud Sql 这是一个 MySql 解决方案 我能够使用 MySql Workbench 进行连接 我如何使用 C 连接MySqlConnectionStringBuilder 我找不到提供这三个证书的

随机推荐

  • Xamarin Toast 消息错误 (C#)

    我想显示一条吐司消息 如果我在 onCreate 中执行此操作 效果会很好 但我想这样做 但出现错误 Java Lang NullPointerException 尝试调用虚拟方法 android content res Resources
  • GCC 链接器脚本 - 将 .bss 部分分割到多个 RAM 区域

    嵌入式设备有两个 SRAM 区域 首先位于 0x20000000 长度为 16k 然后在 0x20040000 处长度为 96k 在应用程序中 生成的 bss 部分大小为 102k 因此它不完全适合任一 RAM 区域 它需要在两个区域之间划
  • 属性路由在区域中不起作用

    场景 我的 ASP NET MVC 5 站点中有一个表单区域 我正在尝试重定向到详细信息操作 该操作使用使用新的属性路由功能定义的自定义路由 重定向到操作 return RedirectToAction Details new slug 我
  • 使用 OpenSSL 解密 .ts 文件

    一切都在同一个目录中 M3u8 文件 EXTM3U EXT X ALLOW CACHE NO EXT X TARGETDURATION 10 EXT X MEDIA SEQUENCE 0 EXT X KEY METHOD AES 128 U
  • Bootstrap 模式样式的位置固定关闭按钮在 Internet Explorer 中无法正确显示

    我正在开发一个网站 该网站使用了引导程序3 0 2版本 我设计了一个模态关闭按钮 它可以在除 Internet Explorer 之外的所有浏览器中正确显示 我已在 ie 11 上检查过 基本上 为了让它看起来像这样 我用过这个CSS mo
  • Django 过滤器错误:“Meta.fields”不得包含非模型字段名称

    我正在使用 Django REST 框架和 django filters 并且我想使用反向关系annotation set作为过滤器之一GET使用模型的 APIDetection 型号如下 class Detection models Mo
  • 使用 React Native 获取设备令牌

    有没有办法通过本机反应获取按需通知的设备令牌 从文档来看 令牌暴露的唯一时间似乎是在 PushNotification 注册事件上 更一般地说 处理设备令牌的常见做法是什么 如果一名用户登录我的应用程序 该应用程序会向 PushNotifi
  • JQuery:根据另一个元素更改高度

    我有两个 DIV 一个 DIV 根据浏览器动态更改大小 响应式设计 我希望另一个 DIV 根据第一个 DIV 的高度修改其高度 我认为最简单的方法是使用 JQuery 动态更改高度 我尝试了以下方法 section div2 css hei
  • 创建分割档案(zip、rar、7z)?

    简而言之 I need使用虚拟安全格式将单个 或多个 文件拆分为多个最大大小的存档 例如 zip 或 rar 任何有效的文件都可以 I 会爱知道某个部分何时完成 回调 这样我就可以开始将其运走 I would 而不是除非不可能 否则请使用
  • 如何重新启动 BaseHTTPServer 实例?

    这就是我所拥有的 http py class HTTPServer def init self port self port port self thread None self run True def serve self self t
  • Android 的 Scala 编程

    我已按照以下教程进行操作斯卡拉和安卓 http www scala lang org node 160 with 斯卡拉2 7 3最终的 生成的 Android 应用程序可以运行 但即使是最基本的应用程序也需要几分钟 来编译并且需要900
  • 尝试完成输入事件,但输入事件接收器已被处理错误

    我不确定我做了什么 但有一段时间我的代码运行顺利 在我添加新活动后出现错误尝试完成输入事件 但输入事件接收器已被处置 我需要有关如何解决此问题的帮助 package proj com desperationfinals import and
  • 如何在 Ruby on Rails 中阻止特定 IP 地址

    我负责一些用 Ruby on Rails 制作的实时网站 我有一些 IP 地址不断攻击这些网站 我想阻止他们的 IP 地址 我知道他们可以使用代理绕过这堵墙 但我确实希望让他们变得更难 并且很想知道我需要在 ruby on Rails 应用
  • 如何在 Selenium IDE 中使用 FirefoxDriver 通过选项使用 setExperimentalOption?

    ChromeOptions options new ChromeOptions options setExperimentalOption useAutomationExtension false options setExperiment
  • 从进程句柄获取进程信息

    我需要得到PROCESS INFORMATION在我的应用程序中使用的外部进程 我有进程句柄和进程 ID 但我不知道如何获取PROCESS INFORMATION出于那个 我正在使用 C 11 Visual Studio 2012 在 Wi
  • 如何根据 Racket Web servlet 中的路径显示不同的内容?

    我正在尝试遵循有关简单网络应用程序的 Racket 指南上的教程 但无法得到一个基本的东西 如何让 servlet 根据请求 URL 提供不同的内容 尽管我进行了搜索 但即使是巨大的博客示例也是一个大文件 并且所有内容都在我背后用巨大的 g
  • 警报对话框按钮太近

    我看到这个东西与警报对话框按钮接触 它们之间没有空格 无论使用什么主题 都会发生这种情况 代码 builder setTitle R string sign in title builder setCancelable false setP
  • Nodejs:使用 async/await 时如何避免嵌套 .then()

    以下是我在 Node js 中尝试执行的操作 Rest API 将城市名称作为输入 我正在尝试使用输入城市的地理编码 API 获取纬度和经度 then 使用纬度和经度 我尝试使用另一个 API 获取最近城市的列表 then 对于所有这些城市
  • 无法找到 XML 模式命名空间的 Spring NamespaceHandler

    我正在 eclipse 中的 tomcat 中运行一些 servlet 代码 一直工作正常 但今天我收到错误Unable to locate Spring NamespaceHandler for XML schema namespace
  • 一笔交易中的多个聚合/存储库

    我有一个支付系统 如下所示 可以通过多张礼券进行支付 礼券随购买一起发放 客户可以使用此礼券进行日后购买 当通过礼券进行付款时 GiftCoupon 表中的UsedForPaymentID 列需要使用该PaymentID 进行更新 对于礼券