Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not completed logic which needs transaction. #48

Open
timsolov opened this issue Dec 26, 2021 · 3 comments
Open

Not completed logic which needs transaction. #48

timsolov opened this issue Dec 26, 2021 · 3 comments

Comments

@timsolov
Copy link

Thank you for your repo and articles. This is awesome work!
I'm trying to rewrite my legacy service to the yours approach.

And I have a question:

How to guarantee that all hours in this command will be updated atomic?

Or what will happen when repo will be updated here:
https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/blob/master/internal/trainings/app/command/schedule_training.go#L56
but one of external service won't be available? Will we have incompleteness here?

@m110
Copy link
Member

m110 commented Dec 26, 2021

Hey @timsolov! Thanks, it's great to hear the project is helpful. 🙂

What you mention is correct, these two places don't have consistent updates. We did this on purpose to show how to refactor it later, but didn't come to this stage yet.

The short answer is, keep things you want to be atomic in a single transaction. So definitely in a single service, where you have control over it.

Another approach is using event-driven patterns: saving one entity in one of the services, and asynchronously updating the other one. In this case, you have to be able to accept eventual consistency.

If your application is just a single service it should be possible to keep database updates within transactions where applicable. Sadly this won't work for external calls — events work great for these. I've described one approach in this example: https://github.com/ThreeDotsLabs/watermill/tree/master/_examples/real-world-examples/transactional-events

@wintermonth2298
Copy link

wintermonth2298 commented Jul 16, 2023

Hi! Thank you for your work, your articles are really cool and useful!

The updateFn approach is awesome, but I'm not sure how to apply it across entities. I'm having difficulty putting 2 tables into one transaction.

I'll give you an example. I need to implement BuyProductCommand(customer, product) where product and customer are two different business entities. How can i do this? It comes to mind to implement the Update method, which will use a single updateFn function with 2 arguments (one for customer, and the second for product), but then the question arises, to which repository should this method be assigned and what should it be called?

Is it generally correct to use this approach for multiple tables?

@m110
Copy link
Member

m110 commented Jul 17, 2023

Hey @wintermonth2298!

This is a common question about this pattern, and the reason usually is thinking about the business entities as SQL tables and modeling the code around them. But they don't need to map 1:1, and bigger entities (aggregates) can easily span multiple tables.

I've described it in this article: https://threedots.tech/post/common-anti-patterns-in-go-web-applications/#starting-with-the-database-schema

The point is there's no rule that CustomerRepository can't know about the Product domain model (or table). If Product is part of the Customer model, the repository needs to know how to handle saving it.

You can consider having a regular Update() method on the customer repository, fetching the product before running it, and calling the domain method of BuyProduct() inside.

product, err := productsRepo.ByID(productID)
err := customerRepo.Update(ctx, func(customer *Customer) error {
    customer.BuyProduct(product)
    return nil
})

Or, consider a dedicated method like BuyProduct(customer, product) directly on the repository if you also bump the number of bought copies on the product model. I'd say it's not a big deal which repository has this method then, it's an implementation detail anyway.

I hope this helps! As always, the context matters, and I don't know your domain well enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants