Post

GRASP Controller pattern in Python

Welcome to the first post from the GRASP series. GRASP stands for General Responsibility Assignment Software Principles. It is a great aid for Object-Oriented Design (but not really exclusive for OOP!). It is all about putting responsibilities in code structures such as classes/methods/modules in such a way it "makes sense".

Controller - what is it?

According to the GRASP definition, the Controller is the first object that receives and handles any interaction with the system coming from the User Interface. At the same time, it is considered a good design to decouple UI from logic. Hence, preferably controller should not be one of the UI objects.

It was all more clear in times of writing desktop apps. Back then, we used technologies like Java Swing or QT (BTW you can use it in Python). When an application has classes for windows, buttons etc it is more obvious they are not a place for business logic. A separate set of objects, more business-oriented would handle it better. When action was to be triggered from the UI, the actual effort for performing action was elsewhere than a UI component.

This experience coming from building desktop apps can still inspire frontend developers and frameworks authors. However, on the backend things did not become clear. When we build backend service with REST(ful)/GraphQL/gRPC API, we do not have a User Interface anymore. Or do we?

API is your UI

Even though as web apps developers we no longer build UI, our applications do have an interface - their API. The difference is that API is consumed by other systems or applications, not a human.

Now, let's consider a Django/Flask/etc application. Let it be a simple TODO list application - adding tasks, viewing and removing them. Let's say it has a RESTful API with a few routes:

GET /lists/<list_slug>/tasks
POST /lists/<list_slug>/tasks
DELETE /lists/<list_slug>/tasks/<task_id>

...and no templates - there is a separate frontend application written in a JS/TS framework that is now trendy.

from collections import defaultdict

from flask import Flask, jsonify

app = Flask(__name__)


lists = defaultdict(list)
ids = iter(range(10_000))


@app.route("/lists/<list_slug>/tasks")
def get_tasks(list_slug: str):
    return jsonify(lists[list_slug])

@app.route("/lists/<list_slug>/tasks", methods=["POST"])
def add_task(list_slug: str):
    lists[list_slug].append({"task_id": next(ids), "text": "test"})
    return jsonify(lists[list_slug])

@app.route("/lists/<list_slug>/tasks/<task_id>", methods=["DELETE"])
def detach_task_from_list(list_slug, task_id: int):
    lists[list_slug] = [t for t in lists[list_slug] if t["task_id"] != int(task_id)]
    return jsonify(lists[list_slug])

Which code structure is a GRAPS Controller here? A view! It performs all the logic (even though it is a very simple one). But given what I wrote previously - a view is also a part of the interface. Is it bad to make view a GRASP controller then? As always, that depends. For simple use cases - like the one from a tutorial-like todo list app above - it's fine. There are no if statements, no branches, separate handling for different types of users - child's play.

World beyond tutorials

Tragically, the real-world projects are not nearly as simple as that TODO list example. Complexity grows if only we keep working on a project and adding new features. If you look at apps like ClickUp.com (which I personally used to use) they surely have lots of interesting code and cases to handle. I hope their code looks nice :D When things get complicated, the burden of framework-specific (interface-specific) stuff becomes a problem. For RESTful example, these are:

  • routing
  • validation
  • authentication
  • serialization
  • HTTP response codes
  • perhaps HTTP caching

The list may vary, depending on the framework and general project architecture. The thing is that when we make our view a GRASP Controller, it is going to change for different reasons.

GRASP recipe for Controller

Assign the responsibility to a class representing one of the following choices:

- Represents the overall "system", a "root object", a device that the software is running within or a major subsystem - these are all variations of a facade controller.

- Represents a use case scenario within which the system event occurs, often named <UseCaseName>Handler, <UseCaseName>Coordinator, or <UseCaseName>Session (...).

Applying UML and Patterns 3rd edition, Craig Larman

For the former, consider programming a buying logic for a vending machine.

You can choose a product, pay for it. Optionally, you can cancel the transaction after choosing a product but before paying for it. Regardless of whether the user interface will consist of physical buttons or a touch screen, you'd agree the logic shouldn't be implemented there. Assuming the user interface will only trigger other code, we need a Controller. Since there are not many operations possible and they are strongly connected (high cohesion), a root object might be a good idea:

class VendingMachine:
    def choose_product(self, number: int) -> None:
        ...

    def pay_for_selection(self, credit_card_token: str) -> None:
        ...

    def cancel_selection(self) -> None:
        ...

When a number of operations is much higher and they are not so much interdependant (low cohesion), having many controllers is better idea:

class SettingReminderUseCase:
    ...

class AddingTaskUseCase:
    ...

class ArchivingTaskUseCase:
   ...

class SettingGoalUseCase:
    ...

class AddingListUseCase:
    ...

class ArchivingListUseCase:
    ...

Obviously, functions would also do:

def setting_reminder_use_case():
    ...

def setting_goal_use_case():
    ...

# etc

GRASP Controller versus the Clean Architecture

In the Clean Architecture, the Controller pattern is used explicitly and called Use Case or Interactor. Name Controller does not appear, but what matters is that the responsibility is fulfilled.

class WithdrawingBidUseCase:
    def __init__(self, auctions_repo: AuctionsRepository) -> None:
        self._auctions_repo = auctions_repo

    def withdraw_bids(self, auction_id: AuctionId, bids_ids: List[BidId]) -> None:
        auction = self._auctions_repo.get(auction_id)
        for bid_id in bids_ids:
            auction.withdraw_bid(bid_id)
        self._auctions_repo.save(auction)

For a blog post dedicated to the Clean Architecture, see my article from 2018 or it's updated version - the Clean Architecture in Python in 2021.

GRASP Controller versus CQRS

By the same token, Command + Command Handler from CQRS is a GRASP Controller.

class PlaceBidCommand:
    auction_id: AuctionId
    bidder_id: BidderId
    amount: Money


class PlaceBidHandler:
    def __init__(self, auctions_repo: AuctionsRepository) -> None:
        self._auctions_repo = auctions_repo
 
    def __call__(self, command: PlaceBidCommand) -> None:
       auction = self._auctions_repo.get(command.auction_id)
       auction.place_bid(command.bidder_id, command.amount)
       self._auctions_repo.save(auction)

For more implementation tips on how to implement CQRS, see my blog post about implementing Command Bus with Injector.

What about application service?

Sometimes our system has a lot of Use Cases (hence hinting towards 2nd version of GRASP Controller - separate classes/functions) but some of them fit together for some reason. In such a case, we may lean to group them together in a single class / module, effectively getting Application Service.

class TokenService:
    def mint_token(self, user_id: int, auth_code: str) -> None:
        ...

    def revoke_all_tokens(self, user_id: int) -> None:
        ...

    def get_tokens_statuses(self, user_id: int) -> Dict[TokenId, TokenStatus]:
        ...



Summary

As long as we are dealing with simple logic, we may good with melting Controller into interface-specific class (e.g. API view). When things gets more complex, you may find refactoring Controller out of view a good investment into code quality and readability.

Naturally, you do that from the very start when implementing the Clean Architecture. You have separate Use Case classes after all!

This was the first post from the series about GRASP. Stay tuned for the next parts! There are 9 GRASP patterns in total. I also strongly recommend a book Applying UML and Patterns written by Craig Larman which is an inspiration for this series. It also contains great, non-trivial examples.

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.