Prospettiva DDD
Vorrei iniziare questo esempio dalla prospettiva DDD. Un buon punto di partenza è cercare i limiti di coerenza durante comando (al contrario di query). Di conseguenza, trovo un aggregato, i principali blocchi costitutivi del DDD.
Nel tuo primo caso d'uso, check-in, un comando è di riservare uno slot adatto. Un limite di coerenza è che non deve essere superata una capacità di slot. Quindi un ovvio aggregato è una Slot. Ed è importante che almeno in questo caso d'uso non abbiamo bisogno di un aggregato di parcheggio, dal momento che il suo stato non è cambiato. Quindi ecco un servizio di applicazione per il caso di utilizzo corrente. Serve come luogo di ambientazione ambientale. Tutti i repository, i client API esterni, ecc. Vengono inseriti qui:
class CheckInApplication
{
private $slotRepository;
private $locker;
public function act(Vehicle $vehicle)
{
$this->locker->lock();
if (is_null($slot = $this->slotRepository->getSuitable($vehicle))) {
$this->locker->unlock();
return new NoVacantSpaceResponse();
}
$this->slotRepository->save($slot->reserve($vehicle));
$this->locker->unlock();
return SlotReservedResponse();
}
}
Ed ecco come potrebbe apparire uno slot che si riserva:
class Slot
{
private $id;
private $capacity;
private $stays;
public function __construct(SlotId $id, Capacity $capacity, Stays $stays)
{
$this->id = $id;
$this->capacity = $capacity;
$this->stays = $stays;
}
public function reserve(Vehicle $vehicle)
{
if ($this->capacity < $vehicle->capacity()) {
throw new Exception('Vacant space is not enough');
}
$this->capacity = $this->capacity - $vehicle->capacity();
$this->stays->add(new Stay($this->vehicle, new DateTime('now')));
}
}
Lo slot controlla le sue regole di integrità, che può ospitare un veicolo e passa all'oggetto Soggiorni. È a sua volta responsabile della conoscenza del dominio di Slot di poter ospitare non più di due veicoli:
class Stays
{
private $stays;
public function __construct(array $stays)
{
$this->stays = $stays;
}
public function add(Stay $stay)
{
if (sizeof($this->stays) > 1) {
throw new Exception('Only two cars per slot allowed');
}
$this->stays[] = $stay;
}
}
Puoi anche aggiungere un nuovo Stay
a Stays
, che contiene le informazioni su quale veicolo occupava lo slot e quando. Sarà utile un po 'più tardi.
Ho preso in considerazione due casi d'uso separati dal tuo scenario di pagamento. Quindi il mio secondo è in carica. Dopotutto, nessuno ti lascerà andare fino a quando non ti verrà addebitato. Ecco un servizio di applicazione:
class ChargeVehicleApplicationService
{
private $repository;
public function __construct(SlotRepository $repository)
{
$this->repository = $repository;
}
public function act(Vehicle $vehicle, SlotId $slotId)
{
try {
return new ChargeResponse($this->repository->getById($slotId)->charge($vehicle));
} catch (Exception $e) {
return ErrorResponse($e->getMessage());
}
}
}
La classe
Slot
viene aggiornata con il nuovo metodo:
public function charge(Vehicle $vehicle)
{
$this->stays->get($vehicle)->charge();
}
Penso che sia una buona idea che la classe Stay
calcoli l'addebito stesso. Dopo tutto, è un proprietario dei dati per una logica algoritmica. Ecco qui ( Stay
class):
public function charge()
{
return (DateTime('now') - $this->arrivedAt) * $this->vehicle->capacity();
}
E infine il terzo caso d'uso, rilasciando il veicolo (nella vita reale mi attenersi a un solo termine, ma ora visto che sto scrivendo il codice al volo potrei essere un po 'volubile). Come sempre, ecco un servizio di applicazione:
class CheckoutVehicleApplicationService
{
private $repository;
public function __construct(SlotRepository $repository)
{
$this->repository = $repository;
}
public function act(Vehicle $vehicle, SlotId $slotId)
{
return
$this->repository
->save(
$this->repository
->getById($slotId)
->releaseFrom($vehicle)
)
;
}
}
Ecco la nuova funzionalità di Slot
:
public function releaseFrom(Vehicle $vehicle)
{
$this->capacity = $this->capacity + $vehicle->capacity();
$this->stays->releaseFrom($vehicle);
}
Il metodo% Stay
rimuove solo il Stay
corrispondente:
public function releaseFrom(Vehicle $vehicle)
{
$this->stays =
array_filter(
$this->stays,
function (Stay $stay) use ($vehicle) {
return !$stay->containsVehicle($vehicle);
}
);
}
Quindi, anche se questo modello e questo codice non sono pensati molto bene, ma mostra che con il concetto aggregato in mente e il pensiero degli oggetti nel cuore, si può arrivare a distribuire abbastanza responsabilità tra gli oggetti del dominio. E questo è buono, dal momento che ogni oggetto è abbastanza semplice.
RDD prospettiva
Questo approccio implica accenti leggermente diversi, sebbene abbia gli stessi principi OO fondamentali. E può essere combinato con DDD in qualsiasi modo tu voglia. Quindi quando utilizzo questa tecnica, di solito inizio con una storia di design. Di cosa tratta la tua app? Stai modellando un parcheggio. Il tuo software è tenuto a definire (da chi?) Se c'è una stanza libera per un veicolo, e se c'è, trovare uno slot adeguato al check-in. Alla cassa è necessario pagare un veicolo (da chi?) Una tariffa in base alle dimensioni del veicolo e alla durata del soggiorno.
Il prossimo passo che faccio è identificare gli oggetti candidati e le loro responsabilità. Consideriamo un caso utente per il check-in. Ho un parcheggio - contiene dati sulle slot. Quindi è una buona misura per rispondere alla domanda su uno spazio libero e per trovare uno slot adatto. Implementerebbe questo compito da solo? Io non la penso così Molto probabilmente il parcheggio collaborerà con le slot, che in realtà sono consapevoli del fatto che siano libere o meno, e se lo sono, allora quanto spazio rimane. Se c'è qualche posto libero, può accettare un veicolo. Quindi ho due oggetti con una responsabilità ciascuno.
Tecnicamente, di solito non ci sono repository e ogni oggetto è responsabile della sua responsabilità. Ad esempio, il salvataggio di un oggetto è considerato come la responsabilità di quell'oggetto. Lo stesso vale per la visualizzazione. Quindi il codice di solito sembra davvero strano per la maggior parte dei programmatori OO. Dato che ci sono già un sacco di codice qui, lascio un link di come potrebbe essere questo.