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

Congratulations and question #15

Open
frederikhors opened this issue Mar 30, 2023 · 6 comments
Open

Congratulations and question #15

frederikhors opened this issue Mar 30, 2023 · 6 comments

Comments

@frederikhors
Copy link

Congratulations for your project.

Can I ask you how to deal with db transactions calls in the hexagonal architecture?

@angelocatalani
Copy link
Owner

angelocatalani commented Mar 31, 2023

Thank you, that's an interesting question I also had.

To simplify the problem let's assume that:

  • use cases and services are atomic
  • ports are not atomic

This means that we may have the ARepositoryPort and BRepositoryPort that needs to be invoked by the XService transactionally:

XService<ARepositoryPort, BRepositoryPort>:
    run():
       // how to execute them transactionally ??
       self.a_repo.get_a()
       self.b_repo.get_b()

During my working experience I have never seen a perfectly clean solution to this problem. (Once you know the rules, you can break them). This means that a pragmatic approach will inject the transaction (e.g., the concrete sqlx transaction) as parameter in all the functions that needs to be executed transactionally. We are breaking the rules, the service cannot be unit-tested anymore because it has the database dependency but we can mitigate this with end to end tests.

XService<ARepositoryPort, BRepositoryPort>:
   // we break the dependency rule in the service and consequently in the ports
    run(sqlx_transaction):
       trx = sqlx_transaction.begin() 
       self.a_repo.get_a(trx)
       self.b_repo.get_b(trx)
       trx.commit()

Now, conceptually I think that the cleanest solution is to introduce the ports: TransactionManager and TransactionManagerConnection:

XService<TransactionManager, ARepositoryPort, BRepositoryPort>:
    run():
       trx = self.transaction_manager.begin() // trx is a `TransactionManagerConnection`
       self.a_repo.get_a(trx)
       self.b_repo.get_b(trx)
       trx.commit() 

In this case the ARepositoryPort and BRepositoryPort have both a dependency on another port that is the TransactionManagerConnection.

What do you think? Were you thinking about something different ?

I think a similar pattern can be used to run multiple services transactionally.

I have not implemented yet this approach but I could be doing it in the weekend 👨‍💻

@frederikhors
Copy link
Author

frederikhors commented Mar 31, 2023

This is exactly the issue I'm trying to fix. You got it right.

I'm writing a big document to better represent the issue in a more real-world example.

I'll write you here as soon as I finish it (a few hours I think).

In the meantime, I'll leave you some links that inspired me but unfortunately still haven't helped me solve (I've just started with Rust and I come from languages in which everything is possible and in fact you pay this freedom only later, when the project increase and doesn't scale well).

I hope this can inspire you.

In the meantime thank you.

@frederikhors
Copy link
Author

frederikhors commented Mar 31, 2023

UPDATE: added link for sniper project website.

@angelocatalani
Copy link
Owner

perfect and yeah I forsee a fiercy buttle with lifetimes and ownership

@frederikhors
Copy link
Author

I created https://github.com/frederikhors/rust-clean-architecture-with-db-transactions. I'm still writing the Readme. But you can get the point if you look in main.rs.

InMemory and Postgres repository.

And in services no possibility to use DB transactions.

I'll write also the workarounds I found for this.

@frederikhors
Copy link
Author

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

2 participants