Posty oznaczone etykietą mvc

Co boli MVC

Prowadziliśmy swego czasu dyskusje o logice biznesowej i szukaliśmy rozwiązania starając się znaleźć jej miejsce w MVC. Stosując bowiem ten paradygmat logika naturalnie "rozmywa się" po kontrolerach i modelach (ekstremiści wstawiają ją nawet do widoków). Typowy przykład złego podejścia to kiepskie implementacje ORM (Propel-PHP, DjangoORM - Python):

class User(Model):
   def save(..):
      ...

W tym przypadku Model jest jednocześnie persistance managerem i wykonuje operacje, których nie powinien. Prawidłowe podejście to

class UserManager:
   def save(self, user):
       ....

Który kod łatwiej przetestować? Gdzie znajduje się logika zapisu stanu encji?

Inny przykład:

class Product(Model):
    def calculate_tax(self, tax):
       ...

vs

class TaxCalculator:
    def calculate_tax(self, product):
       ...

Jak widać w drugim podejściu to TaxCalculator zawiera logikę obliczania podatku dla produktu. Możemy ją łatwiej przetestować (bez uruchamiania całego środowiska i persistance) oraz możemy ją wymieniać wprowadzając różne kalkulatory, np. USATaxCalculator, VATTaxCalculator, etc.

Frameworki narzucają konwencje, ale nie ograniczają aż tak naszych działań. Dlatego w moich projektach, ostatnio opartych o Django, stosuję minimalny kod kontrolerów, operacje zamykam w formularzach (mimo, że nie są renderowane) lub funkcjach/metodach wykonujących proces na rzecz jakiegoś obiektu. W ten sposób modele są pozbawione metod będących operacjami na ich samych. Taki kod jest przede wszystkim łatwiej testowalny (mowa o unit tests), oraz nie wymaga uruchamiania i konfigurowania całego środowiska, co czasem bywa problematyczne i jest wolniejsze.

Jeśli już mowa o Django, to wyraźnie widać, gdzie popełniono błędy projektowe. Model już na wstępie posiada dwie operacje, których nie powinien mieć - model.save() i model.delete(). Zapis i odczyt stanu oraz usuwanie modelu powinny być wykonywane przez obiekt zewnętrzny (konkretnie persistence manager). Z kolei formularze są ściśle związane z widgetami HTML, co przy stosowaniu ich jako obiektów utility (np. do walidacji danych) pociąga za sobą niechciane zależności.

Warto zastosować się do kilku reguł:

  • ograniczyć ciało kontrolerów do wywoływania operacji sprowadzając je do roli przetwarzania żądań i generowania odpowiedzi; nie implementować w nich logiki biznesowej (tj. use cases/sekwencji)
  • każdy use case powinien mieć swoją implementację w oddzielnym bycie, co wyraźnie odseparuje warstwę logiki biznesowej
  • nie implementować operacji w klasach modeli, które zawierają logikę biznesową; tj. ograniczać do operacji modyfikujących lub odczytujących stan (w znakomitej większości pozostaną akcesory i mutatory lub po prostu properties)

Ciekawe artykuły: