Does my code follow DDD principles?
I'm trying to model P2P crypto trading using DDD and I'm having trouble modeling the lifecycle of sell orders. Lifecycle: Some trader-seller creates an order. Some trader-buyer responds to it. Buyer confirms the transfer of fiat to the seller's account. Seller confirms the receipt of fiat from the buyer to his account. As a result, I got the following C# code: SellOrder.cs: public class SellOrder { public SellOrder(Guid guid, SellOrderInfo info, Guid sellerGuid, string sellerToExchangerTransferTransactionHash) { if (!ValidateSellOrderInfo(info)) throw new InvariantViolationException("Info is invalid."); if (string.IsNullOrWhiteSpace(sellerToExchangerTransferTransactionHash)) throw new DevelopmentErrorException("Seller to exchanger transfer transaction hash is invalid."); Status = SellOrderStatus.Created; SellerGuid = sellerGuid; SellerToExchangerTransferTransactionHash = sellerToExchangerTransferTransactionHash; BuyerGuid = null; BuyerWalletAddress = null; } private bool ValidateSellOrderInfo(SellOrderInfo info) { // Something } public void Respond(Guid buyerGuid, string buyerWalletAddress) { if (Status != SellOrderStatus.Created) throw new InvariantViolationException("Status is invalid."); if (string.IsNullOrWhiteSpace(buyerWalletAddress)) throw new DevelopmentErrorException("Buyer wallet address is invalid."); BuyerGuid = buyerGuid; BuyerWalletAddress = buyerWalletAddress; Status = SellOrderStatus.BuyerResponded; } public void Confirm(Guid traderGuid) { Status = Status switch { SellOrderStatus.BuyerResponded when traderGuid.Equals(BuyerGuid) => SellOrderStatus.BuyerConfirmed, SellOrderStatus.BuyerResponded => throw new InvariantViolationException("Trader is not a buyer."), SellOrderStatus.BuyerConfirmed when traderGuid.Equals(SellerGuid) => SellOrderStatus.BuyerAndSellerConfirmed, SellOrderStatus.BuyerConfirmed => throw new InvariantViolationException("Trader is not a seller."), _ => throw new InvariantViolationException("Status is invalid.") }; } public SellOrderStatus Status { get; } public Guid Guid { get; } public SellOrderInfo Info { get; } public Guid SellerGuid { get; } public string SellerToExchangerTransferTransactionHash { get; } public Guid? BuyerGuid { get; private set; } public string? BuyerWalletAddress { get; private set; } } As I understand it, the Trader and SellOrder entities are separate aggregation roots. In the SellOrder class, I initially stored references directly to the Trader class objects, but then I learned that aggregation roots should not reference each other directly, and replaced the objects with identifiers. Did I do the right thing? I also have a question about the void Confirm(Guid traderGuid) method: does it violate any DDD principles that I check the trader on whose behalf this method is called? Maybe it would be better to move this check to the application layer or to some domain layer service? I also plan to add a dispute system - if the seller does not confirm receipt of the fiat, a dispute should be automatically created referencing this order. What is the best way to implement this? I think domain events would be the perfect solution here.

I'm trying to model P2P crypto trading using DDD and I'm having trouble modeling the lifecycle of sell orders. Lifecycle:
- Some trader-seller creates an order.
- Some trader-buyer responds to it.
- Buyer confirms the transfer of fiat to the seller's account.
- Seller confirms the receipt of fiat from the buyer to his account.
As a result, I got the following C# code:
SellOrder.cs:
public class SellOrder
{
public SellOrder(Guid guid, SellOrderInfo info, Guid sellerGuid, string sellerToExchangerTransferTransactionHash)
{
if (!ValidateSellOrderInfo(info))
throw new InvariantViolationException("Info is invalid.");
if (string.IsNullOrWhiteSpace(sellerToExchangerTransferTransactionHash))
throw new DevelopmentErrorException("Seller to exchanger transfer transaction hash is invalid.");
Status = SellOrderStatus.Created;
SellerGuid = sellerGuid;
SellerToExchangerTransferTransactionHash = sellerToExchangerTransferTransactionHash;
BuyerGuid = null;
BuyerWalletAddress = null;
}
private bool ValidateSellOrderInfo(SellOrderInfo info)
{
// Something
}
public void Respond(Guid buyerGuid, string buyerWalletAddress)
{
if (Status != SellOrderStatus.Created)
throw new InvariantViolationException("Status is invalid.");
if (string.IsNullOrWhiteSpace(buyerWalletAddress))
throw new DevelopmentErrorException("Buyer wallet address is invalid.");
BuyerGuid = buyerGuid;
BuyerWalletAddress = buyerWalletAddress;
Status = SellOrderStatus.BuyerResponded;
}
public void Confirm(Guid traderGuid)
{
Status = Status switch
{
SellOrderStatus.BuyerResponded when traderGuid.Equals(BuyerGuid) => SellOrderStatus.BuyerConfirmed,
SellOrderStatus.BuyerResponded => throw new InvariantViolationException("Trader is not a buyer."),
SellOrderStatus.BuyerConfirmed when traderGuid.Equals(SellerGuid) => SellOrderStatus.BuyerAndSellerConfirmed,
SellOrderStatus.BuyerConfirmed => throw new InvariantViolationException("Trader is not a seller."),
_ => throw new InvariantViolationException("Status is invalid.")
};
}
public SellOrderStatus Status { get; }
public Guid Guid { get; }
public SellOrderInfo Info { get; }
public Guid SellerGuid { get; }
public string SellerToExchangerTransferTransactionHash { get; }
public Guid? BuyerGuid { get; private set; }
public string? BuyerWalletAddress { get; private set; }
}
As I understand it, the Trader and SellOrder entities are separate aggregation roots. In the SellOrder class, I initially stored references directly to the Trader class objects, but then I learned that aggregation roots should not reference each other directly, and replaced the objects with identifiers. Did I do the right thing?
I also have a question about the void Confirm(Guid traderGuid) method: does it violate any DDD principles that I check the trader on whose behalf this method is called? Maybe it would be better to move this check to the application layer or to some domain layer service?
I also plan to add a dispute system - if the seller does not confirm receipt of the fiat, a dispute should be automatically created referencing this order. What is the best way to implement this? I think domain events would be the perfect solution here.