Yleinen ongelma Railsin kanssa työskennellessä on päättää, mihin ominaisuuksiemme logiikka sijoitetaan.
Logiikka on usein sijoitettu ohjaimiin, malleihin tai, jos olemme onnekkaita, palveluobjektiin. Jos meillä on siis palvelukohteita, miksi tarvitsemme käyttötapauksia?
Seuraa minua tässä artikkelissa ja tutustu tämän kuvion etuihin.
Käyttötapaus
Määritelmä
Käyttötapaus on luettelo toimista tai tapahtumavaiheista, jotka tyypillisesti määrittelevät roolin ja järjestelmän välisen vuorovaikutuksen tavoitteen saavuttamiseksi.
On syytä mainita, että tätä mallia sovelletaan monin eri tavoin ja sillä on vaihtoehtoisia nimiä. Voimme löytää sen nimellä Vuorovaikuttajat, Operaattorit tai Komennot, mutta Ruby yhteisö, jonka kanssa pysymme Käyttötapaus. Jokainen toteutus on erilainen, mutta sillä on sama tarkoitus: palvella käyttäjän järjestelmän käyttötarkoitusta.
Vaikka meidän projekti emme määrittele vaatimuksia käyttämällä Käyttötapauss ja UML, tämä malli on edelleen hyödyllinen liiketoimintalogiikan jäsentämisessä käytännöllisellä tavalla.
Säännöt
Meidän Käyttötapaukset on oltava:
Puitteet eivät ole riippuvaisia
Tietokannasta riippumaton
Vastaa vain yhdestä asiasta (määrittele vaiheet käyttäjän tavoitteen saavuttamiseksi).
Edut
Luettavuus: Helppo lukea ja ymmärtää, koska vaiheet on määritelty selkeästi.
Kytkennästä irrottaminen: Siirrä logiikka ohjaimista ja malleista ja luo uusi abstraktiotaso.
Näkyvyys: Koodipohja paljastaa järjestelmässä käytettävissä olevat ominaisuudet.
Käytäntöön
Otetaan esimerkki käyttäjästä, joka haluaa ostaa jotain järjestelmässämme.
moduuli UseCases
moduuli Ostaja
luokka Purchase
def initialize(ostaja:, ostoskori:)
@buyer = ostaja
@cart = cart
end
def call
return unless check_stock
return unless create_purchase
notify end
private
attr_reader :buyer, :cart
def check_stock
Services::CheckStock.call(cart: cart)
end
def create_purchase
Services::CreatePurchase.call(ostaja: ostaja, kori: kori).call
end
def notify
Services::NotifyBuyer.call(ostaja: ostaja)
end
end
end
end
Kuten voitte nähdä tässä koodi Esimerkissä luotiin uusi Käyttötapaus nimeltään Purchase. Määritimme vain yhden julkisen metodin soita. Kutsumenetelmän sisältä löytyy ostoksen tekemisen melko perustavanlaatuiset vaiheet, ja kaikki vaiheet on määritelty yksityisiksi metodeiksi. Jokainen vaihe kutsuu Service Objectia, ja näin meidän Käyttötapaus määrittelee vain ostoksen tekemisen vaiheet, ei itse logiikkaa. Näin saamme selkeän kuvan siitä, mitä järjestelmässämme voidaan tehdä (tehdä ostos) ja mitkä ovat sen toteuttamiseen tarvittavat vaiheet.
Nyt olemme valmiita kutsumaan ensimmäistä Käyttötapaus ohjaimesta.
luokka Controller
def osto
UseCases::Buyer::Purchase.new(
ostaja: purchase_params[:ostaja],
cart: purchase_params[:cart]
).call
...
end
...
end
Tästä näkökulmasta katsottuna Käyttötapaus näyttää melko samalta kuin Service Object, mutta sen tarkoitus on erilainen. Palveluobjekti suorittaa matalan tason tehtävän ja on vuorovaikutuksessa järjestelmän eri osien, kuten tietokannan, kanssa, kun taas Käyttötapaus luo uusi korkean tason abstraktio ja määrittelee loogiset vaiheet.
Parannukset
Ensimmäinen Käyttötapaus toimii, mutta voisi olla parempi. Miten voisimme parantaa sitä? Hyödynnetään kuiva jalokiviä. Tässä tapauksessa käytämme Kuiva-transaktio.
Määritellään ensin perusluokka.
luokka UseCase
include Dry::Transaction
class << self
def call(**args)
new.call(**args)
end
end
end
Tämä auttaa meitä siirtämään attribuutteja UseCase-tapahtumaan ja käyttämään niitä. Sen jälkeen olemme valmiita määrittelemään Purchase-käyttötapauksen uudelleen.
moduuli UseCases
moduuli Ostaja
luokka Purchase
def initialize(ostaja:, ostoskori:)
@buyer = ostaja
@cart = cart
end
def call
return unless check_stock
return unless create_purchase
notify
end
private
attr_reader :ostaja, :ostoskori
def check_stock
Services::CheckStock.call(cart: cart)
end
def create_purchase
Services::CreatePurchase.call(ostaja: ostaja, ostoskori: ostoskori).call
end
def notify
Services::NotifyBuyer.call(ostaja: ostaja)
end
end
end
end
Uusien muutosten ansiosta näemme selkeästi, miten vaiheet on määritelty, ja voimme hallita jokaisen vaiheen tuloksia Success()- ja Failure()-toiminnoilla.
Olemme valmiita kutsumaan uutta käyttötapausta ohjaimessa ja valmistelemaan vastauksen lopputuloksesta riippuen.
luokka Controller
def osto
UseCases::Buyer::Purchase.new.call(
ostaja: purchase_params[:ostaja],
cart: purchase_params[:cart]
) do |result|
result.success do
...
end
result.failure do
...
end
end
...
end
...
end
Tätä esimerkkiä voisi parantaa vielä enemmän validoinneilla, mutta tämä riittää osoittamaan tämän mallin tehon.
Päätelmät
Ollaanpa rehellisiä. Käyttötapausmalli on melko yksinkertainen ja näyttää hyvin paljon Service Objectilta, mutta tämä abstraktiotaso voi muuttaa sovelluksesi huomattavasti.
Kuvittele, että uusi kehittäjä liittyy projektiin ja avaa kansion use_cases. Ensivaikutelmana hän näkee luettelon kaikista järjestelmässä saatavilla olevista ominaisuuksista, ja kun hän avaa yhden käyttötapauksen, hän näkee kaikki kyseisen ominaisuuden edellyttämät vaiheet menemättä syvälle logiikkaan. Tämä järjestyksen ja hallinnan tunne on tämän mallin suurin etu.
Ota tämä työkalupakkiin, ja ehkä tulevaisuudessa voit käyttää sitä hyödyksesi.