Ruby on Rails modularisatie met Packwerk Aflevering I
Mensen vinden het moeilijk om het grote plaatje van een probleem te zien zonder er veel tijd en moeite in te steken. Dit gebeurt vooral bij het werken met grote en complexe toepassingen....
Een veel voorkomend probleem bij het werken met Rails is beslissen waar we de logica van onze functies moeten plaatsen.
De logica wordt vaak geplaatst in de Controllers, Modellen, of als we geluk hebben in een Service Object. Dus als we Service Objecten hebben, waarom hebben we dan Use Cases nodig?
Volg mij in dit artikel om de voordelen van dit patroon te ontdekken.
Een use case is een lijst van acties of event-stappen die typisch de interacties definiëren tussen een rol en een systeem om een doel te bereiken.
Het is het vermelden waard dat dit patroon op veel verschillende manieren wordt toegepast en alternatieve namen heeft. We kunnen het vinden als Interactoren, Bedieningspersoneel of Opdrachtenmaar in de Ruby gemeenschap waar we bij blijven Gebruikscasus. Elke implementatie is anders, maar heeft hetzelfde doel: het gebruik van het systeem door de gebruiker dienen.
Zelfs als in onze project we de vereisten niet definiëren met behulp van Gebruikscasuss en UML is dit patroon nog steeds nuttig om de bedrijfslogica op een praktische manier te structureren.
Onze Gebruikscases moet zijn:
Laten we het voorbeeld nemen van een gebruiker die iets wil kopen in ons systeem.
module UseCases
module Koper
klasse Aankoop
def initialiseer(koper:, winkelwagen:)
@koper = koper
@cart = winkelwagen
einde
def oproep
terugkeren tenzij check_voorraad
retour tenzij create_purchase
melden einde
privé
attr_lezer :koper, :winkelwagen
def controleer voorraad
Services::CheckStock.call(winkelwagen: winkelwagen)
einde
def maak_aankoop
Diensten::Aankoop aanmaken.call(koper: koper, winkelwagen: kar).call
einde
def verwittigen
Diensten::Informeerkoper.oproep(koper: koper)
einde
einde
einde
einde
Zoals je kunt zien in deze code voorbeeld hebben we een nieuwe Gebruikscasus genaamd Kopen. We hebben slechts één openbare methode gedefinieerd bel. In de aanroepmethode vinden we vrij eenvoudige stappen om een aankoop te doen en alle stappen zijn gedefinieerd als privémethoden. Elke stap roept een Service Object aan, zodat onze Gebruikscasus definieert alleen de stappen om een aankoop te doen en niet de logica zelf. Dit geeft ons een duidelijk beeld van wat er in ons systeem kan worden gedaan (een aankoop doen) en de stappen om dat te bereiken.
Nu zijn we klaar om onze eerste Gebruikscasus van een controller.
klasse controller
def aankoop
UseCases::Buyer::Purchase.new(
koper: aankoop_params[:koper],
winkelwagen: aankoop_params[:winkelwagen]
).aanroepen
...
einde
...
einde
Vanuit dit perspectief is de Gebruikscasus lijkt veel op een Service Object, maar het doel is anders. Een serviceobject voert een taak op laag niveau uit en heeft interactie met verschillende onderdelen van het systeem, zoals de database, terwijl de Use Case creëert een nieuwe abstractie op hoog niveau en definieert de logische stappen.
Onze eerste Gebruikscasus werkt, maar kan beter. Hoe kunnen we het verbeteren? Laten we gebruik maken van de droog edelstenen. In dit geval gebruiken we droogtransactie.
Laten we eerst onze basisklasse definiëren.
klasse UseCase
include Dry::Transaction
klasse << zelf
def aanroep(**args)
new.call(**args)
einde
einde
einde
Dit zal ons helpen om attributen door te geven aan de UseCase transactie en ze te gebruiken. Dan zijn we klaar om onze Purchase Use Case opnieuw te definiëren.
module UseCases
module Koper
klasse Aankoop
def initialiseer(koper:, winkelwagen:)
@koper = koper
@cart = winkelwagen
einde
def oproep
return tenzij check_stock
retour tenzij create_purchase
verwittig
einde
privé
attr_lezer :koper, :winkelwagen
def controleer voorraad
Services::CheckStock.call(winkelwagen: winkelwagen)
einde
def maak_aankoop
Diensten::Aankoop aanmaken.call(koper: koper, winkelwagen: winkelwagen).call
einde
def verwittigen
Diensten::Informeerkoper.oproep(koper: koper)
einde
einde
einde
einde
Met de nieuwe wijzigingen kunnen we op een duidelijke manier zien hoe onze stappen zijn gedefinieerd en kunnen we het resultaat van elke stap beheren met Success() en Failure().
We zijn klaar om onze nieuwe Use Case aan te roepen in de controller en onze respons voor te bereiden, afhankelijk van het eindresultaat.
klasse controller
def aankoop
UseCases::Buyer::Purchase.new.call(
koper: purchase_params[:koper],
winkelwagen: aankoop_params[:winkelwagen].
) do |result|
resultaat.succes doen
...
einde
resultaat.mislukking doen
...
einde
einde
...
einde
...
einde
Dit voorbeeld zou nog verbeterd kunnen worden met validaties, maar dit is genoeg om de kracht van dit patroon te laten zien.
Laten we eerlijk zijn, de Gebruikspatroon is vrij eenvoudig en lijkt veel op een Service Object, maar dit abstractieniveau kan een grote verandering teweegbrengen in je applicatie.
Stel je voor dat een nieuwe ontwikkelaar bij het project komt en de map use_cases opent, dan krijgt hij als eerste indruk een lijst met alle features die beschikbaar zijn in het systeem en na het openen van één Use Case ziet hij alle benodigde stappen voor die feature zonder diep in de logica te duiken. Dit gevoel van orde en controle is het grote voordeel van dit patroon.
Neem dit mee in je gereedschapskist en misschien zul je het in de toekomst goed kunnen gebruiken.