I want to begin this by first addressing the problem space here as I see a bit of misunderstanding regarding both "eventual consistency" and DDD.
DDD is a design process that understands real-world problems can sometimes reach a level of complexity that they cannot be cleanly separated and modeled into completely independent units. That is, process itself can have rules and when such a situation arises, eventual consistency can be employed to offer a solution. I don't read the quote you offer from the blue book (in the OP comments) as advocating for any mode of consistency, rather pointing out that it is often a desirable and sometimes necessary attribute within a system of which we need to be aware when forming our expectations.
Whether or not a given system needs to employ an eventual consistent paradigm should be determined through analysis of its requirements in terms of scaling, not through analysis of it's design (though the former can certainly inform the latter). This decision should not be made lightly (and doesn't have to be all-or-nothing), because the trade-offs are steep and often requires much greater complexity in terms of implementation where employed.
As to your system, I think you may have complected a couple of things together.
The first is that an Auction
would be better understood/implemented as a Saga
/ProcessManger
rather than an Aggregate
. That is, an Auction
represents a grouping of related events that must be managed in terms of process. In this way, your Auction
is given the responsibility to oversee it's own consistency and can therefore usefully abstract business requirements.
This flows into the second issue of not separating responsibilities into the correct entities. The issue you describe is predicated on your Auction
needing to "ask" for an account balance. Clearly, it should be the responsibility of your Account
(UserBalance
is a confusing name) to keep track of the current balance. Therefore, it must also be responsible for creating a new Bid
. In this way, we can cleanly separate the logic necessary for verifying an account balance from that of placing bids. Nobody at an auction cares about account balances, so let's keep that separate from our auction. Think of it like this, "how does an auction work?". It can be modeled relatively simply:
PlaceBidHandler (moves money into escrow):
// throws NotFound
account = accounts.Find( cmd.UserId )
// throws InsuficientFunds or raises `BidPlaced`
account.PlaceBid( cmd.AuctionId, cmd.LotNumber, cmd.Amount )
SubmitBidHandler (keeps track of all Bids
or maybe just highest Bid
):
// throws NotFound
auctioneer = auctioneers.Find( cmd.AuctionId )
// raises Outbidded
auctioneer.ReceiveCompetingBid( cmd.UserId, cmd.LotNumber, cmd.Amount )
RevokeBidHandler (removes money from escrow):
// throws NotFound
account = accounts.Find( cmd.UserId )
account.RevokeBid( cmd.AuctionId, cmd.LotNumber )
The Auction
(which mediates the process) is simply responsible for reacting to BidPlaced
events in a way to trigger SubmitBid
OR RevokeBid
commands (should NotFound
be thrown), and reacting to Outbidded
events in a way to trigger RevokeBid
commands.
If you would like to keep Account
and Auction
in separate contexts (you don't want Account.PlaceBid
), this can be modified further to add an extra layer along the lines of: PlaceOrder -> PaymentReceived
(in this case ordering a Bid
and debiting account) and BidRevoked -> RefundPayment
to cleanly separate accounts from auctions.