1

Let's say I have a controller called MessageReceiverController which is a controller of an API that other services can call and include messages in the requests.

For each of the messages the controller receives, it will process it and then send it to API_A. If response from API_A is successful, it will send the message to API_B too. There are a few operations to perform based on this logic.

When I'm writing my controller and service for the controller, should I split the operations in the service class or should I combine all of them into a single method in the service class and then let the controller call that one single method to perform everything?

To make things clearer, here are some pseudocodes as examples of what I mean.

Example 1

In this example, the actions are merged into a single method in the service class. I may have split the actions within the service class for clarify but the controller is essentially only calling a method in its service that performs multiple actions.

class MessageReceiverService {

  private _sendToApi(message, apiUrl) {
    return this.request.post(apiUrl, {
      body: message
    });
  }

  processAndSendAll(messages) {
    // Put all the actions together into a single method in the service
    foreach (message in messages) {
      message.data = 'Process message with some data';
      const response = this._sendToApi('http://API_A', {
        body: message
      });
      if (response.status === 200) {
        this._sendToApi('http://API_B', {
          body: message
        })
      }
    }
  }
}

class MessageReceiverController {

  receiveAction(messages) {
    this.messageReceiverService.processAndSendAll(messages);
    return { status: 200 }
  }
}

Example 2

Or should I split the actions in the service class and then make use of the controller class to perform the individual actions like so? I don't feel like this is a good approach because it feels like part of the logic is leaked into the controller, but I might be wrong.

class MessageReceiverService {
  process(message) {
    message.data = 'Process message with some data';
    return message;
  }

  send(message, apiUrl) {
    this.request.post(apiUrl, {
      body: message
    })
  }
}

class MessageReceiverController {

  receiveAction(messages) {
    foreach (message in messages) {
      const processedMessage = this.messageReceiverService.process(message);
      const response = this.messageReceiverService.send(processedMessage, 'http://API_A');
      if (response.status === 200) {
        this.messageReceiverService.send(processedMessage, 'http://API_B')
      }
    }
    return { status: 200 }
  }
}

Example 3

Or, lastly, should I split the actions but encapsulate the loops and some logic in the service class like in this example? In this case, the controller is almost doing like "Action 1, Action 2, Action 3... and so on". However, it may not be very efficient in the code as, in this case, the loop could have just looped through the messages once instead of twice. Again, I'm not really sure what would be the right approach and this is one of the ideas that came to me.

class MessageReceiverService {
  processAllMessages(messages) {
    foreach (message in messages) {
      message.data = 'Process message with some data';
    }
    return messages;
  }

  sendAllToApi(messages) {
    foreach (message in messages) {
      const response = this.request.post('http://API_A', {
        body: message
      });
      if (response.status === 200) {
        this.request.post('http://API_B', {
          body: message
        })
      }
    }
  }
}

class MessageReceiverController {

  receiveAction(messages) {
      const processedMessages = this.messageReceiverService.processAllMessages(messages);
      this.messageReceiverService.sendAllToApi(processedMessages);

      return { status: 200 }
  }
}

So, when it comes to operations in controller that consist of multiple actions within it, how should I organise the logic in the controller and its service? Is it better to have the controller only be calling a single method on the service to perform its action or it is it better to split them into multiple methods in the service class for the controller to call?

Which of the examples I have above is more appropriate, or if neither of them are, how should I write my controller and service classes?

2
  • Don't architect something that "either receives data from a queue or can be directly called." Because the first case actually consists of a separate [object ...] that calls the second. Mar 14, 2021 at 20:47
  • @MikeRobinson Oh... I was actually trying to illustrate an example that the controller we see could be the kind of controller that receives a message from a queue or the kind of controller that receives a message when being called on. Apologies, I should have been clearer. But you made a good point there. I’ve updated my question to make it more concise to reduce the confusion. Thanks!
    – xenon
    Mar 14, 2021 at 20:59

2 Answers 2

2

Never forget that each of your layers exists for a specific purpose, and you should sort your logic into those layers according to that purpose.

  • Controllers' purpose is to handle web requests. They are mainly focused on mapping the appropriate action/data to the correct service/model. They are not concerned with intricate business logic and should mainly be structured as a simple passthrough to your services. At best, they validate basic model validity.
  • Services' purpose is to house your business logic. They contain the orchestration of the operations that your codebase is built on. This means that services speak to your external dependencies (db, third party API, ...) and inner logic (domain) to achieve the needed outcome.

Orchestrating the calls made to several APIs is inherently business logic, and should therefore be done in the service layer, i.e. your example 1.

Or should I split the actions in the service class and then make use of the controller class to perform the individual actions like so? I don't feel like this is a good approach because it feels like part of the logic is leaked into the controller

You're completely correct here, this is putting some of the logic, i.e. the orchestration inbetween the calls, onto the controller, which is not something the controller should occupy itself with.

It also doesn't quite make sense from a design perspective. When ordering a meal at a restaurant, do you list off the steps of the recipe to the waiter? Or do you just tell them what you want and let the chef figure it out for themselves how to make that meal?

Or, lastly, should I split the actions but encapsulate the loops and some logic in the service class like in this example?

This is the same offloading of logic onto the controller, but in a less severe fashion. The response remains the same, it's not really the controller's job to do these kinds of things.

It also doesn't quite make sense from a design perspective. Imagine if you order a burger, and the chef comes out of the kitchen to give you a freshly cooked meat patty, and then tells you to give him that same patty so he can make your burger.

0

I would suggest separating the code into separate methods so that:

  1. Processing logic is separated into its own method. This way you would be able to unit-test the logic itself, if needed.

  2. Calls to APIs are encapsulated into respective methods. This way code structure is clearer and your methods would be more in line with the single responsibility principle.

In terms of processing all messages and then making all the calls to API A and B against doing processing and making calls message by message. This depends on the amount of messages and time it takes to process a single message - if there is only a handful of messages and processing takes milliseconds, there is no difference what approach to take. However, if there are hundreds, thousands or millions of messages, it might be would be best to do the processing and make calls message by message to avoid processor/network utilisation spikes.

3
  • By separating methods, do you mean something along Example 2 or Example 3 in the question?
    – xenon
    Mar 15, 2021 at 1:31
  • @xenon, something like Example 2.
    – spektro37
    Mar 15, 2021 at 23:10
  • @xenon, after reading Flater's response, I would suggest going with what he is suggesting. My main point was to have each step separated into a method that does one thing, but really none of the examples is doing this.
    – spektro37
    Mar 15, 2021 at 23:25

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.