Ruby on Rails-modularisering med Packwerk Episode I
Mennesker har svært ved at se det store billede af et problem uden at bruge en masse tid og kræfter på det. Det sker især, når man arbejder med store og komplekse applikationer....
Et almindeligt problem, når man arbejder med Rails, er at beslutte, hvor logikken fra vores funktioner skal placeres.
Logikken er ofte placeret i Controllers, Models eller, hvis vi er heldige, i et Service Object. Så hvis vi har serviceobjekter, hvorfor har vi så brug for use cases?
Følg mig i denne artikel for at opdage fordelene ved dette mønster.
En use case er en liste over handlinger eller hændelsestrin, der typisk definerer interaktionen mellem en rolle og et system for at nå et mål.
Det er værd at nævne, at dette mønster anvendes på mange forskellige måder og har alternative navne. Vi kan finde det som Interaktører, Operatører eller Kommandoermen i Ruby fællesskab, vi holder os til Brugssag. Hver implementering er forskellig, men med samme formål: at tjene en brugers brug af systemet.
Selv om vi i vores projekt Vi definerer ikke kravene ved hjælp af Brugssags og UML er dette mønster stadig nyttigt til at strukturere forretningslogikken på en praktisk måde.
Vores Brugsscenarier skal være:
Lad os tage et eksempel med en bruger, der vil købe noget i vores system.
modul UseCases
modul Køber
klasse Køb
def initialize(køber:, indkøbsvogn:)
@køber = køber
@kurv = kurv
end
def opkald
return unless check_stock
return medmindre create_purchase
give besked end
privat
attr_reader :buyer, :cart
def check_stock
Services::CheckStock.call(vogn: vogn)
end
def create_purchase
Services::CreatePurchase.call(buyer: buyer, cart: cart).call
end
def notify
Services::NotifyBuyer.call(køber: køber)
end
end
end
end
Som du kan se i denne Kode eksempel oprettede vi en ny Brugssag der hedder Purchase. Vi har kun defineret én offentlig metode opkald. Inde i opkaldsmetoden finder vi ret grundlæggende trin til at foretage et køb, og alle trin er defineret som private metoder. Hvert trin kalder et serviceobjekt, på denne måde er vores Brugssag definerer kun trinene til at foretage et køb og ikke selve logikken. Det giver os et klart billede af, hvad der kan gøres i vores system (foretage et køb), og hvilke trin der skal til for at opnå det.
Nu er vi klar til at kalde vores første Brugssag fra en controller.
klasse Controller
def køb
UseCases::Buyer::Purchase.new(
buyer: purchase_params[:buyer],
cart: purchase_params[:cart]
).call
...
slut
...
slut
Ud fra dette perspektiv er Brugssag ligner stort set et serviceobjekt, men formålet er anderledes. Et serviceobjekt udfører en opgave på lavt niveau og interagerer med forskellige dele af systemet som f.eks. databasen, mens Use Case skaber en ny abstraktion på højt niveau og definerer de logiske trin.
Vores første Brugssag fungerer, men kunne være bedre. Hvordan kan vi forbedre det? Lad os gøre brug af tør ædelstene. I dette tilfælde vil vi bruge tør-transaktion.
Lad os først definere vores basisklasse.
klasse UseCase
inkluderer Dry::Transaction
klasse << self
def call(**args)
new.call(**args)
end
end
end
Det vil hjælpe os med at sende attributter til UseCase-transaktionen og bruge dem. Så er vi klar til at omdefinere vores Purchase Use Case.
modul UseCases
modul Køber
klasse Køb
def initialize(køber:, indkøbsvogn:)
@køber = køber
@kurv = kurv
end
def opkald
return unless check_stock
return medmindre create_purchase
underret
end
privat
attr_reader :buyer, :cart
def check_stock
Services::CheckStock.call(vogn: vogn)
slut
def create_purchase
Services::CreatePurchase.call(buyer: buyer, cart: cart).call
end
def notify
Services::NotifyBuyer.call(køber: køber)
end
end
end
end
Med de nye ændringer kan vi på en klar måde se, hvordan vores trin er defineret, og vi kan styre resultatet af hvert trin med Success() og Failure().
Vi er klar til at kalde vores nye Use Case i controlleren og forberede vores svar afhængigt af det endelige resultat.
klasse Controller
def køb
UseCases::Buyer::Purchase.new.call(
køber: purchase_params[:buyer],
cart: purchase_params[:cart]
) do |result|
result.success do
...
end
result.failure do
...
end
end
...
slut
...
slut
Dette eksempel kunne forbedres endnu mere med valideringer, men det er nok til at vise styrken i dette mønster.
Lad os være ærlige her, den Use Case-mønster er ret enkel og ligner meget et serviceobjekt, men dette abstraktionsniveau kan gøre en stor forskel i din applikation.
Forestil dig, at en ny udvikler kommer ind i projektet og åbner mappen use_cases, så får han som første indtryk en liste over alle de funktioner, der findes i systemet, og når han har åbnet en use case, kan han se alle de nødvendige trin for den pågældende funktion uden at gå i dybden med logikken. Denne følelse af orden og kontrol er den største fordel ved dette mønster.
Tag det med i din værktøjskasse, og måske vil du få god brug for det i fremtiden.