Ruby on Rails-modularisering med Packwerk Episode I
Mennesker har vanskelig for å se helheten i et problem uten å bruke mye tid og krefter på det. Dette skjer spesielt når vi jobber med store og komplekse applikasjoner....
Et vanlig problem når vi jobber med Rails, er å bestemme hvor vi skal plassere logikken fra funksjonene våre.
Logikken er ofte plassert i kontrollerne, modellene eller, hvis vi er heldige, i et tjenesteobjekt. Så hvis vi har Service Objects, hvorfor trenger vi da Use Cases?
Følg meg i denne artikkelen for å oppdage fordelene med dette mønsteret.
Et brukstilfelle er en liste over handlinger eller hendelsestrinn som vanligvis definerer samspillet mellom en rolle og et system for å oppnå et mål.
Det er verdt å nevne at dette mønsteret brukes på mange forskjellige måter og har alternative navn. Vi kan finne det som Samspillere, Operatører eller Kommandoermen i Ruby samfunnet vi holder oss til Brukssak. Hver implementering er forskjellig, men med samme formål: å tjene brukerens bruk av systemet.
Selv om vi i vår prosjekt Vi definerer ikke kravene ved hjelp av Brukssaks og UML er dette mønsteret fortsatt nyttig for å strukturere forretningslogikken på en praktisk måte.
Vår Brukstilfeller må være:
La oss ta et eksempel med en bruker som ønsker å kjøpe noe i systemet vårt.
modul UseCases
modul Buyer
klasse Kjøp
def initialize(kjøper:, handlekurv:)
@kjøper = kjøper
@kurv = kurv
end
def call
return unless check_stock
return unless create_purchase
notify end
private
attr_reader :buyer, :cart
def check_stock
Services::CheckStock.call(handlevogn: handlevogn)
end
def create_purchase
Services::CreatePurchase.call(buyer: buyer, cart: cart).call
end
def notify
Services::NotifyBuyer.call(kjøper: kjøper)
end
end
end
end
Som du kan se i denne kode eksempel opprettet vi en ny Brukssak som heter Purchase. Vi definerte bare én offentlig metode samtale. Inne i anropsmetoden finner vi ganske grunnleggende trinn for å foreta et kjøp, og alle trinnene er definert som private metoder. Hvert trinn kaller et tjenesteobjekt, slik at vår Brukssak definerer bare stegene for å gjennomføre et kjøp, ikke selve logikken. Dette gir oss et klart bilde av hva som kan gjøres i systemet vårt (foreta et kjøp) og stegene for å oppnå det.
Nå er vi klare til å kalle opp vår første Brukssak fra en kontroller.
klassen Controller
def kjøp
UseCases::Buyer::Purchase.new(
buyer: purchase_params[:buyer],
cart: purchase_params[:cart]
).call
...
end
...
slutt
Ut fra dette perspektivet er Brukssak ser omtrent ut som et Service Object, men formålet er et annet. Et serviceobjekt utfører en oppgave på lavt nivå og samhandler med ulike deler av systemet, for eksempel databasen, mens Brukstilfelle skaper en ny abstraksjon på høyt nivå og definerer de logiske trinnene.
Vår første Brukssak fungerer, men kan bli bedre. Hvordan kan vi forbedre det? La oss gjøre bruk av tørr edelstener. I dette tilfellet skal vi bruke tørrtransaksjon.
La oss først definere baseklassen vår.
klasse UseCase
include Dry::Transaction
class << self
def call(**args)
new.call(**args)
end
end
end
Dette vil hjelpe oss med å sende attributter til UseCase-transaksjonen og bruke dem. Da er vi klare til å definere UseCase Purchase på nytt.
modul UseCases
modul Buyer
klasse Kjøp
def initialize(kjøper:, handlekurv:)
@kjøper = kjøper
@kurv = kurv
end
def call
return unless check_stock
return unless create_purchase
notify
end
private
attr_reader :kjøper, :handlekurv
def sjekk_lager
Services::CheckStock.call(handlevogn: handlevogn)
end
def create_purchase
Services::CreatePurchase.call(buyer: buyer, cart: cart).call
end
def notify
Services::NotifyBuyer.call(kjøper: kjøper)
end
end
end
end
Med de nye endringene kan vi se på en tydelig måte hvordan stegene våre er definert, og vi kan håndtere resultatet av hvert steg med Success() og Failure().
Vi er klare til å kalle opp vårt nye Use Case i kontrolleren og forberede svaret vårt avhengig av det endelige resultatet.
klassen Controller
def kjøp
UseCases::Buyer::Purchase.new.call(
kjøper: purchase_params[:buyer],
cart: purchase_params[:cart]
) do |result|
result.success do
...
end
result.failure do
...
end
end
...
slutt
...
slutt
Dette eksempelet kan forbedres ytterligere med valideringer, men det er nok til å vise hvor effektivt dette mønsteret er.
La oss være ærlige her, den Bruksmønster er ganske enkel og ligner mye på et Service Object, men dette abstraksjonsnivået kan utgjøre en stor endring i applikasjonen din.
Tenk deg at en ny utvikler kommer inn i prosjektet og åpner mappen use_cases. Ved første øyekast får han en liste over alle funksjonene som er tilgjengelige i systemet, og når han åpner en use case, ser han alle de nødvendige trinnene for den aktuelle funksjonen uten å gå dypt inn i logikken. Denne følelsen av orden og kontroll er den største fordelen med dette mønsteret.
Ta dette med deg i verktøykassen, og kanskje vil du få god bruk for det i fremtiden.