Product :: RubyOnRails → Haskell
Plan
History of the solution
(Poland)
Restaumatic
https://www.restaumatic.com/pl/
Michal Kozakiewicz
Maciej Bielecki
Kamil Figiela
Adam Szlachta
… and others
History of the solution
History of the solution
History of the solution
History of the solution
History of the solution
History of the solution
History of the solution
History of the solution
History of the solution
History of the solution
History of the solution
History of the solution
Architecture and Design
Frontend, Backend
Frontend
PureScript
Specular (FRP)
Backend
Haskell
Servant (HTTP server)
Opaleye (DB layer)
ADTs (Data Model)
Ruby On Rails
PostgreSQL
JavaScript
Records (DB Model)
JavaScript
Migrations
Freer Monad
REST
JSON RPC
Embedding
aeson (serialization)
Data Model: Algebraic Data Types (ADTs)
data Invoice = Invoice� { _companyId :: CompanyId� , _invoiceNumber :: Text
, _totalAmount :: Money� , _paidAmount :: Money� , _invoiceDate :: Day� }� deriving (Eq, Show, Ord, Generic)��type Invoices = [Invoice]�
data Company = Company� { _companyId :: CompanyId� , _name :: Text
, _iban :: IBAN� , _email :: EMail� }� deriving (Eq, Show, Ord, Generic)�
type Companies = [Company]
JSON serialization: aeson
data Invoice = Invoice� { _companyId :: CompanyId� , _invoiceNumber :: Text
, _totalAmount :: Money� , _paidAmount :: Money� , _invoiceDate :: Day� }� deriving (Eq, Show, Ord, Generic)��instance FromJSON Invoice where parseJSON� = genericParseJSON stripLensPrefix��instance ToJSON Invoice where toJSON� = genericToJSON stripLensPrefix
DB Model: Records
Alternatives:
type instance HsFields InvoicesTable =� '[ "company_id" :-> CompanyId� , "invoice_number" :-> Text� , "total_amount" :-> Money� , "paid_amount" :-> Money� , "invoice_date" :-> UTCTime� , "created_at" :-> UTCTime� , "updated_at" :-> UTCTime� ]��fromDBInvoice :: DBInvoice -> Invoice�fromDBInvoice dbInvoice = Invoice� { _payment = #company_id dbInvoice� , _totalAmount = #invoice_number dbInvoice� , _totalAmount = #total_amount dbInvoice� , _paidAmount = #paid_amount dbInvoice� , _invoiceDate = #invoice_date dbInvoice� }
DB Layer: Opaleye
Alternatives:
findInvoices� :: ( DBQueryEff effs )� => Text� -> Eff effs Invoices�findInvoices number = do� invoices <- query (\inv -> #invoice_number .== number)� pure $ fromDBInvoice <$> invoices��saveInvoice� :: ( Member TimeL effs, DBUpdateEff effs )� => Invoice� -> Eff effs InvoiceId�saveInvoice invoice = do� let rec = #id =: Nothing� <+> #company_id =: (invoice ^. companyId)� <+> #invoice_number =: (invoice ^. invoiceNumber)� <+> #total_amount =: (invoice ^. totalAmount)� <+> #paid_amount =: (invoice ^. paidAmount)� <+> #invoice_date =: (invoice ^. invoiceDate)� insertWithTimestamp @InvoiceTable rec
HTTP Server: Servant
Alternatives:
type InvoicesAPI =� ("api" :> "v1" :> "billing" :> "invoices"� :> ( QueryParam "companyId" CompanyId� :> Throws NotAuthorized� :> Throws NotFound� :> Get '[JSON] Invoices� :<|> ReqBody '[JSON] Invoice� :> Throws NotAuthorized� :> Throws NotFound� :> PostCreated '[JSON] NoContent� )� )��invoicesServer
:: ServerEff effs
=> ServerT InvoicesAPI (Eff effs)�invoicesServer = runDomain ... getInvoices� :<|> runDomain ... createInvoice
Business Logic: Freer Monad & Effects
Alternatives:
data InvoiceL a where� CreateInvoice :: InvoiceData -> InvoiceL (Result Invoice)� GetInvoice :: InvoiceId -> InvoiceL (Result Invoice)� UpdateInvoice :: InvoicePatch -> InvoiceL (Result Invoice)� DeleteInvoice :: InvoiceId -> InvoiceL (Result ())���assignInvoice� :: ( Member InvoiceL effs, Member CompanyL effs )� => CompanyId -> InvoiceId -> Eff effs Company�assignInvoice companyId invoiceId = do� ensureCompanyExists companyId� invoice <- withSuccess $ getInvoice invoiceId� updateCompany $ nilCompanyPatch
{ _companyId = companyId, _invoices = Add invoice }
Web UI: Specular FRP
invoicesTable� :: forall m� . MonadWidget m� => Invoices� -> m (Dynamic (Array InvoiceMark))�invoicesTable invoices = do� table $ do� caption $ h3 $ text "Invoices"� thead $ do� tr $ do� th $ text "Mark to pay"� th $ text "Number"� th $ text "Invoice date"� th $ text "Total amount"� th $ text "Unpaid amount"� tbody $ traverse (makeInvoiceRow config) invoices
Haskell in production
Haskell in production
Thank you for watching!
Alexander Granin
graninas@gmail.com, Twitter: @graninas