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(*args, **kwargs):
pass
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):
pass
Który kod łatwiej przetestować? Gdzie znajduje się logika zapisu stanu encji?
Inny przykład:
class Product(Model):
def calculate_tax(self, tax):
pass
vs
class TaxCalculator:
def calculate_tax(self, product):
pass
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:
Opublikowano w kategoriach