Jak być może pamiętasz, w ramach wyzwania 100 commitów dotykam domeny parkingu. Popełniłem przy tworzeniu tej aplikacji naprawdę sporo błędów, ale z jednej rzeczy na ten momenty jestem mega zadowolony. Po kilku iteracjach doszedłem do wniosku, że użytkownicy mogą zgłaszać zapotrzebowanie na całe miejsce postojowe lub jego część. Nie jest to rezerwacja, a właśnie zapotrzebowanie. Z zapotrzebowaniem mogą robić co chcą: dodawać, anulować czy też zmieniać (tej możliwości nie zaimplementowałem na ten moment). Jednak te operacje są dostępne dopóki nie nadejdzie magiczna godzina, która sprawi, że zapotrzebowanie staje się rezerwacją. Wtedy reguły gry kompletnie się zmieniają. Rezerwacja nie może być już beztrosko anulowana. Rezygnacja z niej może prowadzić do poniesienia opłaty przez klienta czy też odebrania mu części punktów lojalnościowych. Dlaczego o tym piszę? Bo z perspektywy kodu mamy obecną duplikację kodu.

Poniższa klasa znajduje się w module parkowania.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Value
public class OpenParkingSpot implements ParkingSpot {

  @NonNull
  ParkingSpotInformation parkingSpotInformation;

  public Either<ParkingSpotOccupationFailed, ParkingSpotOccupiedEvents> occupy(VehicleId vehicleId, VehicleSize vehicleSize) {
    ParkingSpotOccupation parkingSpotOccupation = getParkingSpotOccupation();
    if (!parkingSpotOccupation.canHandle(vehicleSize)) {
      return announceFailure(new ParkingSpotOccupationFailed(getParkingSpotId(), vehicleId, "there is not enough space for vehicle"));
    }

    ParkingSpotOccupied parkingSpotOccupied = new ParkingSpotOccupied(getParkingSpotId(), vehicleId, vehicleSize);
    if (parkingSpotOccupation.occupyWith(vehicleSize).isFull()) {
      return announceSuccess(events(getParkingSpotId(), parkingSpotOccupied, new FullyOccupied(getParkingSpotId())));
    }
    return announceSuccess(events(getParkingSpotId(), parkingSpotOccupied));
  }

}

Natomiast ta umiejscowiona jest w module zgłaszania zapotrzebowania na miejsce postojowe.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Value
public class ParkingSpotRequests {

  ParkingSpotId parkingSpotId;
  ParkingSpotOccupation requestedOccupation;
  Set<RequestId> requests;

  public Either<StoringParkingSpotRequestFailed, RequestForPartOfParkingSpotStored> storeRequest(RequestId requestId, VehicleSize vehicleSize) {
    if (!requestedOccupation.canHandle(vehicleSize)) {
      return announceFailure(new StoringParkingSpotRequestFailed(parkingSpotId, requestId, "not enough parking spot space"));
    }
    return announceSuccess(new RequestForPartOfParkingSpotStored(parkingSpotId, requestId, vehicleSize));
  }

  public Either<StoringParkingSpotRequestFailed, RequestForWholeParkingSpotStored> storeRequest(RequestId requestId) {
    if (!requests.isEmpty()) {
      return announceFailure(new StoringParkingSpotRequestFailed(parkingSpotId, requestId, "there are requests for this parking spot"));
    }
    return announceSuccess(new RequestForWholeParkingSpotStored(parkingSpotId, requestId));
  }

  public Either<ParkingSpotRequestCancellationFailed, ParkingSpotRequestCancelled> cancel(RequestId requestId) {
    if (!requests.contains(requestId)) {
      return announceFailure(new ParkingSpotRequestCancellationFailed(parkingSpotId, requestId, "there is no such request on that parking spot"));
    }
    return announceSuccess(new ParkingSpotRequestCancelled(parkingSpotId, requestId));
  }

  public boolean cannotHandleMore() {
    return requestedOccupation.isFull();
  }

  public boolean isFree() {
    return requests.isEmpty();
  }

}

Chciałbym zwrócić uwagę na metody OpenParkingSpot.occupy(VehicleId, VehicleSize) oraz ParkingSpotRequests.storeRequest(RequestId, VehicleSize). W obydwu miejscach sprawdzamy czy jest wystarczająca ilość miejsca, aby pomieścić dany pojazd. W pierwszej jest to niezmiennik pilnujący czy pojazd fizycznie, w aktualnym momencie, zmieści się na miejscu postojowym. Natomiast drugi sprawdza czy potencjalnie się on zmieści, jeśli będzie więcej niż jedna rezerwacja na to miejsce. Dla przypomnienia dodam, że model biznesowy w tej abstrakcyjnej domenie zakłada możliwość zaparkowania np. dwóch motocyklów na jednym miejscu postojowym. Wracając, niby ten sam kod, ale będący w innym kontekście.

W przyszłości mogłoby przyjść wymaganie pozwalające robić tzw. overbooking. Czyli przy tworzeniu zapotrzebowania na miejsce postojowe moglibyśmy dopuścić np. 3 motocykle pomimo tego, że zmieszczą się tylko 2. Natomiast, gdy dojdzie do fizycznego parkowania to nie ma takiej możliwości, aby te 3 motocykle weszły. Dlatego ten niezmiennik znajdujący się w metodzie OpenParkingSpot.occupy(VehicleId, VehicleSize) jest nie do ruszenia. Co o tym sądzisz? Daj znać w komentarzu.