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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions