-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathReservationUseCase.hs
130 lines (105 loc) · 5.05 KB
/
ReservationUseCase.hs
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
module UseCases.ReservationUseCase
( listAll
, fetch
, tryReservation
, cancel
, availableSeats
, Persistence
, ReservationError (..)
, Dom.Reservation (..)
, Dom.ReservationMap
)
where
import qualified Data.Map.Strict as M
import Data.Maybe (fromMaybe)
import Data.Time.Calendar
import qualified Domain.ReservationDomain as Dom (Reservation (..),
ReservationMap (),
addReservation,
availableSeats,
cancelReservation,
isReservationPossible)
import Polysemy
import Polysemy.Error
import Polysemy.Input ()
import Polysemy.Trace (Trace, trace)
import UseCases.KVS (KVS, getKvs, insertKvs, listAllKvs)
import Numeric.Natural
{--
This module specifies the Use Case layer for the Reservation system.
It coordinates access to Effects and the actual domain logic.
The module exposes service functions that will be used by the REST API in the External layer.
Implemented Use Cases:
1. Display the number of available seats for a given day
2. Enter a reservation for a given day and keep it persistent.
If the reservation can not be served as all seats are occupies prode a functional error message stating
the issue.
3. Display the list of reservations for a given day.
4. Delete a given reservation from the system in case of a cancellation.
NO functional error is required if the reservation is not present in the system.
5. Display a List of all reservation in the system.
All Effects are specified as Polysemy Members.
Interpretation of Effects is implemented on the level of application assembly, or in the context of unit tests.
Please note: all functions in this module are pure and total functions.
This makes it easy to test them in isolation.
--}
-- | Persistence is a key/value store Day / [Reservation]
type Persistence = KVS Day [Dom.Reservation]
-- | The functional error, raised if a reservation is not possible
newtype ReservationError = ReservationNotPossible String -- deriving (Show, Eq)
-- | compute the number of available seats for a given day.
-- | Implements UseCase 1.
availableSeats :: (Member Persistence r, Member Trace r) => Day -> Sem r Natural
availableSeats day = do
trace $ "compute available seats for " ++ show day
todaysReservations <- fetch day
return $ Dom.availableSeats maxCapacity todaysReservations
-- | the maximum capacity of the restaurant.
-- | to keep things simple this just a constant value of 20.
-- | In real life this would kept persistent in a database, and would be accessed by yet another abstract effect.
maxCapacity :: Natural
maxCapacity = 20
-- | try to add a reservation to the table.
-- | Return Just the modified table if successful, else return Nothing
-- | implements UseCase 2.
tryReservation :: (Member Persistence r, Member (Error ReservationError) r, Member Trace r) => Dom.Reservation -> Sem r ()
tryReservation res@(Dom.Reservation date _ _ requestedQuantity) = do
trace $ "trying to reservate " ++ show requestedQuantity ++ " more seats on " ++ show date
todaysReservations <- fetch date
let available = Dom.availableSeats maxCapacity todaysReservations
if Dom.isReservationPossible res todaysReservations maxCapacity
then persistReservation res
else throw $ ReservationNotPossible ("Sorry, only " ++ show available ++ " seats left on " ++ show date)
where
-- | persist a reservation to the reservation table.
persistReservation :: (Member (KVS Day [Dom.Reservation]) r, Member Trace r) => Dom.Reservation -> Sem r ()
persistReservation r@(Dom.Reservation day _ _ _ ) = do
trace $ "enter a new reservation to KV store: " ++ show r
rs <- fetch day
let updated = Dom.addReservation r rs
trace $ "storing: " ++ show updated
insertKvs day updated
-- | fetch the list of reservations for a given day from the key value store.
-- | If no match is found, an empty list is returned.
-- | Implements UseCase 3.
fetch :: (Member Persistence r, Member Trace r) => Day -> Sem r [Dom.Reservation]
fetch day = do
trace $ "fetch reservations for " ++ show day
maybeList <- getKvs day
return $ fromMaybe [] maybeList
-- | cancel a reservation, that is: delete it from the system.
-- | Implements UseCase 4.
cancel :: (Member Persistence r, Member Trace r) => Dom.Reservation -> Sem r ()
cancel res@(Dom.Reservation date _ _ _) = do
trace $ "deleting reservation " ++ show res
reservations <- fetch date
trace $ "before: " ++ show reservations
let after = Dom.cancelReservation res reservations
trace $ "after: " ++ show after
insertKvs date after
-- | list all entries from the key value store and return them as a ReservationMap
-- | Implements UseCase 5.
listAll :: (Member Persistence r, Member Trace r) => Sem r Dom.ReservationMap
listAll = do
trace "listing all reservation entries"
fmap M.fromList listAllKvs