-
-
Save piq9117/823dce0f2bada6d29442da1e02972cdb to your computer and use it in GitHub Desktop.
{-# LANGUAGE EmptyDataDecls #-} | |
{-# LANGUAGE FlexibleContexts #-} | |
{-# LANGUAGE GADTs #-} | |
{-# LANGUAGE GeneralizedNewtypeDeriving #-} | |
{-# LANGUAGE MultiParamTypeClasses #-} | |
{-# LANGUAGE OverloadedStrings #-} | |
{-# LANGUAGE QuasiQuotes #-} | |
{-# LANGUAGE TemplateHaskell #-} | |
{-# LANGUAGE TypeFamilies #-} | |
module Main where | |
import Database.Persist | |
import Database.Persist.Postgresql | |
import Database.Persist.TH | |
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| | |
Person | |
name String | |
age Int Maybe | |
deriving Show | |
|] | |
getPerson :: MonadIO m => PersionId -> ( Maybe ( Entity Person ) ) | |
getPerson pid = selectFirst [ PersionId ==. pid ] [ ] | |
connStr = "host=localhost dbname=persistent_expirement user=user password=user port=5432" | |
main :: IO () | |
main = runStderrLoggingT $ withPostgresqlPool connStr 10 $ \pool -> liftIO $ do | |
flip runSqlPersistMPool pool $ do | |
runMigration migrateAll |
Also when we take this approach, you probably don't need classes as granular as GetPerson
. You probably want classes like DatabaseRead
, DatabaseWrite
, LocationService
, FileSystemRead
, FileSystemWrite
, Log
, and similar things. The point of writing such bespoke classes is twofold: (1) hide the details of third-party libraries (e.g. selectFirst
) and runtime dependencies (e.g. MyBackend
); and (2) get an idea of what each function might be doing (and not doing) from its signature.
Along those lines, you should never need to write a function with a MonadIO
constraint: if you need to do some IO
, write a bespoke class for it in Classes
and write an instance of that class for App
in AppWiring
. Try not to make your classes general-purpose things like Fetch
. Make them represent particular services that your application needs, like LocationService
. For example a class like
class Fetch m where
fetch :: Url -> m Response
doesn't hide the details of a service from application developers. Neither does it guarantee that the correct URL is used or that the response is parsed correctly. Instead, using a bespoke class like
class LocationService where
findLocation :: Coordinates -> m Location
means you only need to construct the URL
and parse the Response
in one place, in the LocationService
instance for App
.
Remember that the point of these bespoke classes isn't for them to be reused in other projects: the point is to abstract the dependencies of this particular application only and to segregate I/O action by class constraints to enforce principle of least privilege.
10/n
When you take this approach, use a module structure analogous to this:
The important characteristic about the above module structure is that
GetPerson
doesn't depend on Persist,App
, orMyBackend
. This extends to every submodule ofAppLogic
:GetPerson
completely abstracts those dependencies. The only place where we couple our application to Persist is in theGetPerson
instance forApp
.9/n