DEV Community

Mateusz Gajda
Mateusz Gajda

Posted on • Edited on

Do you know GRASP? Part 1 - Controller and Creator

Due to the fact that shorter posts are much better read, I decided to divide this topic into about 4 separate entries. Each concerning two or three patterns. Here is part one.

What is it?

GRASP - General Responsibility Assignment Software Patterns are 9 patterns, which will help you with adding a new responsibility to your current codebase. It could be very helpful because sometimes we don't know if this component is suitable for this functionality. Maybe we should create a new one? These 9 patterns can help us.

Controller

Problem: What the first object beyond the UI layer receives and coordinates “controls” a system operation?

Controllers image

In the software world, everything should have its responsibility. Models contain data, views present the data, repositories talk with databases, etc. Our systems are consistent with a lot of different objects and components. Very often they have to talk with each other, but we should take care who's talking to whom.
We do not want to let our objects talk directly to the database or other external services. We need something which takes this responsibility.

By simplifying our systems, we can say that they consist of many different use cases. Each use case needs to talk with a lot of different objects. We need to talk with the database, execute some operations on the domain object or retrieve some data from external API. We need also a layer that will be responsible for handling user input.

That is why we a controller. An object which will be an entry point for our use case or system. It will be the first object beyond the UI layer. Very often it will be an application service or command handler if we are talking in CQRS case. But, be careful! It isn't a controller such as in MVC. In MVC the controller is still part of a UI. In our case, it is the first object in the system. Let's take a look:

export class OrderService {
  private orderRepository: OrderRepository;
  private productRepository: ProductRepository;
  private constructor(
    orderRepository: OrderRepository, 
    productRepository: ProductRepository) {
    this.orderRepository = orderRepository;
    this.productRepository = productRepository;
  }
  async create(orderDetails: OrderDetailsDto) {
    const { quantity, productId } = orderDetails;

    const product = await this.productRepository.get(productId);
    const order = Order.create(quantity, product);

    await this.orderRepository.save(order);
  }
}

We have our application service called OrderService. Here we talk to our database through repositories. We retrieve a product which next is passed to our static factory method, which is part of our domain model Order. Next, we save a new order to the database. Thanks to that, the domain model doesn't know anything about the database. Additionally, the separation of the application code from the UI allows us to test our service more easily. In the case of the unit tests, we need only to mock our repositories.
Here is how it looks like in our router:

export const orderRouting = (orderService: OrderService) => {
  const router = express.Router();
  router.post("/order", (req: Request, res: Response, next: express.NextFunction) => {
    orderService
      .create({
        productId: req.body.productId,
        quantity: req.body.quantity,
      })
      .then(result => res.json(result))
      .catch(err => next(err));
  });

  return router;
};

Creator

Problem: Who creates object Y?

Lego creator

A common problem is definitely which object should be responsible for creating a class Y instance. Creating an object is an important process, so it is good to have defined rules that will make it easier to decide who should create a Y-instance. So let's take a look at these rules. Object X can create Y if the following conditions are true:

  1. X aggregate/contains Y.
  2. X has all the data required to create Y
  3. X closely use Y

Thanks to these rules, it may turn out that we don't have to create a special factory to create the Y object. We can simply create it in object X. Why? Because X object needs Y object, it knows everything about how to create Y object so a new factory, in this case, will be useless.

Assume that we have a diary class that contains our notes. We can easily create our note inside the Diary, because it contains the list of Notes.

export class Diary {
  notes: Notes[];

  addNote(title: string, content: string){
    this.notes.push(Notes.create(title, content))
  }
}

Summary

That's only 2 out of 9 patterns. In the next few parts, I'll introduce you to the next ones. I hope you'll find them useful and use them. If you have some question, feel free to write a comment :)

Top comments (0)