Skip to content

A small review of the proposed architecture up to branch 3 #6

@frederikhors

Description

@frederikhors

So in the last few days, I tried to change one of my app codebases to the one you are suggesting (the latest example of it right now is the branch 3).

I found a bit of issues which I hope we'll find a way to fix in the subsequent branches:

  1. How do I write traits implementation on multiple files?

    This is important to me. I have tons of methods and I cannot have everything in one file only.

  2. What if a repository method must be called within the same transaction?

    This is another big issue currently not handled.

  3. I lose the ergonomics of writing this in the service method:

    impl Service {
        pub async fn buy_product(&self, customer_id: &str, id: &str) -> Result<()> {
            // This is just a fake code and a fake example
    
            let transaction = self.repo.begin(); // note begin() is custom and not leaks DB details to service layer
    
            let product = transaction.get_product(id).await?; // repo method
    
            let balance = transaction.check_money_balance(customer_id).await?; // repo method
    
            // This is just a simple example, but there can be many business rules here
            if balance < product.price() {
                return Err("not enough money left");
            }
    
            transaction.buy_product(id, customer_id).await?; // repo method
    
            transaction.commit(); // again, DB details not leaked to the service layer here
    
            Ok(())
        }
    }

    and instead, I have to call a specific repo method passing all the details and above all I'm forced to move some logic functions in the repo method:

    impl Service {
        pub async fn buy_product(&self, customer_id: &str, id: &str) -> Result<()> {
            // Since I cannot start transactions in the service anymore
            // I need to call a repo method to do everything in it instead for the DB transaction:
    
            let result = self.repo.buy_product(customer_id, id).await?; // repo method
    
            Ok(())
        }
    }
    
    impl Db {
        pub async fn buy_product(&self, customer_id: &str, id: &str) -> Result<()> {
            // This is the same code used to be in the service method
    
            let transaction = self.repo.begin();
    
            let product = self.get_product(transaction, id).await?; // repo method
    
            let balance = self.check_money_balance(transaction, customer_id).await?; // repo method
    
            // This kind of rule needs to be in the service layer, not in the repo one
            if balance < product.price() {
                return Err("not enough money left");
            }
    
            self.buy_product(transaction, id, customer_id).await?; // repo method
    
            transaction.commit();
    
            Ok(())
        }
    }
  4. To be able to fix the previous point I need to create a repo method for each service method (and for the Trait), which is a mess to maintain: many files to write and update, many Trait declarations to write/update: a mess.

  5. I can't call methods of other services in the same transaction (but I have to say that this point doesn't interest me much, in fact, it's wrong as you explain very well in your excellent guide).

    The simple example is this:

    impl Service {
        pub async fn buy_product(&self, customer_id: &str, id: &str) -> Result<()> {
            // This is just a fake code and a fake example
    
            let transaction = self.repo.begin(); // note begin() is custom and not leaks DB details to service layer
    
            self.settings_service.get_customer_sell_settings(transaction, customer_id).await?;
    
            // do something else
    
            transaction.commit(); // again, DB details not leaked to the service layer here
    
            Ok(())
        }
    }

    This is useful and usable only if settings_service is within the same server, maybe in the same codebase, you know: if it's very fast to respond.

It's all for now. I'll update this issue if I find something else to suggest to you. Thanks for everything.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions