2

I am building a Loopback4 app just for learning; I have a scenario where I have to check for a couple of things before sending that request to the database, so my entry point is my Controllers Method, and it is not something that you can resolve with database constraints like foreign keys.

As my application's entry point is my Controller's Action, I can place the logic there. If I have to execute the same code in my app again, I do not want to invoke that code by sending that call as an extra REST Request, so shall I place my logic in the repository, that repository can be called by any object, or create a new service class and delegate evertyhing there so AccountController is just a simple entry point, and delegates everything to the Service Layer.

AccountController{
    // option 1 - validate in service entry point, which is controller
    async create(
      @requestBody()
      account: Omit<Account, 'Id'>,
    ): Promise<Account> {
      account.validate();
      return this.accountRepository.create(account);
    }

    // option 2 - leave validation to repository
    async create(
      @requestBody()
      account: Omit<Account, 'Id'>,
    ): Promise<Account> {
      // validation logic is in accountRepository
      return this.accountRepository.create(account);
    }

    // option 3 - create a new service layer, which is accessible all around
    // and leave validation to the service layer
    async create(
      @requestBody()
      account: Omit<Account, 'Id'>,
    ): Promise<Account> {
      return this.accountService.create(account);
    }
}    
    AccountService {
      function create(account){
        account.validate();
        return this.accountRepository.create(account);
      }
    }

How would you do this on a Monolithic app, in a microservice app, still sticking to DDD.

0

Having a separate service is a good idea, based on the principle of separation of concerns. It gives you a clean boundary between the parts of your code that need to understand HTTP, and the parts that do not.

So this is a good start:

async create(
  @requestBody()
  account: Omit<Account, 'Id'>,
): Promise<Account> {
  return this.accountService.create(account);
}

You should also be thinking about the fact that the requestBody is a message defined by the schema in your published API, not a domain entity. They are very closely related, but over a long enough period of time you'll discover that they change for different reasons.

So if Account is a schema in your API, then your controller signature is fine... but you are possibly introducing confusion by coupling your domain model to the API schema.

If Account is a domain entity here, then your dependency arrows are pointing the wrong direction -- the domain model is yours, to implement as you like, but your API is a contract that you have agreed to with your consumers, and is correspondingly more expensive to change. We don't want things that are expensive to change depending on things that are intended to change.

It might clarify the distinction if you make the messages first class concerns in your implementation. That might look like

async create(
  @requestBody()
  createAccount: CreateAccount,
): Promise<AccountCreated> {
  return this.accountService.create(createAccount);
}
|improve this answer|||||
  • Thank you, all very good points. – hazimdikenli Dec 3 '19 at 5:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.