javascriptmicroservicesdomain-driven-designevent-sourcingeventual-consistency

How do I properly design Aggregate in DDD, Event-sourcing


Suppose I want to make an e-commerce system. I have 2 aggregates here ProductAggregate and UserAggregate. Product aggregate contains productId, price. User aggregate contains userId and balance. Here's the problem, in event-sourcing we should not rely on the read model since there might be eventual consistency problem. Ok so we should rely on the command model right I guess?, but this two command model is different. I read from somewhere else they told me that aggregate should only rely on its state. Let's say the user want to buy a product I have to check if he has enough balance and in order to do that I need to know the price of product. So read model not allowed, aggregate query not allowed. what options do I have here?

const ProductAggregate = {
    state: {
        productId: "product-1",
        price: 100
    }
}

const UserAggregate = {
    state: {
        userId: "userId-1",
        balance: 50
    },
    handlePurchase: ({ userId, productId }) => {
        // todo I got productId from the client, but how can I retrieve its price ?
        if (this.state.balance < price) {
            throw "Insufficient balance bro."
        }
    }
}

So I though it must be my bad aggregate design which makes UserAggregate requires state from outside of its context. So in this situation how do I properly design an Aggregate for User and Product.

edited:

I have been thinking all day long for the solution and I came up with this approach. So instead of putting purchase command in the UserAggregate I put it in the ProductAggregate and call it OrderProductCommand which is a bit weird for me since the product itself can't create an order, but the user can (it seems to work anyway I don't even know?). So with this approach I can now retrieve the price and send another command DeductBalanceCommand which will deduct amount of money from the user.

const ProductAggregate = {
    state: {
        productId: "product-1",
        price: 100
    },
    handleOrder: ({productId, userId}) => {
        await commandBus.send({
            command: "handleDeduct",
            params: {
                userId: userId,
                amount: this.state.price
            }
        })
        .then(r => eventBus.publish({
            event: "OrderCreated",
            params: {
                productId: productId,
                userId: userId
            }
        }))
        .catch(e => {
            throw "Unable to create order due to " + e.message
        })
    }
}

const UserAggregate = {
    state: {
        userId: "userId-1",
        balance: 50
    },
    handleDeduct: ({ userId, amount }) => {
        if (this.state.balance < amount) {
            throw "Insufficient balance bro."
        }

        eventBus.publish({
            event: "BalanceDeducted",
            params: {
                userId: userId,
                amount: amount
            }
        })
    }
}

Is it fine and correct to use this approach? it's a bit weird for me or maybe it's just a way of thinking in DDD world?

ps. I added javascript tag so my code can have colors and easy to read.


Solution

  • First of all, regarding your handle, you're not stupid :)

    A few points: