Skip to content

Instantly share code, notes, and snippets.

@aerohit
Created August 7, 2020 07:25
Show Gist options
  • Save aerohit/bb0264f751c877f5f1fc38f53c78eb9e to your computer and use it in GitHub Desktop.
Save aerohit/bb0264f751c877f5f1fc38f53c78eb9e to your computer and use it in GitHub Desktop.
{-# LANGUAGE GeneralisedNewtypeDeriving #-}
module HZK202007080846
()
where
import Control.Monad.Identity
import Test.Hspec
-- TAGS :tagless-final:
-- Reference: https://jproyo.github.io/posts/2019-03-17-tagless-final-haskell.html
-- Unit tests are provided below
newtype UserName = UserName String deriving Show
newtype Data = Data String deriving (Show, Eq)
newtype AppConfig = AppConfig String deriving Show
newtype LogMsg = LogMsg String deriving Show
class Monad m => Cache m where
getFromCache :: UserName -> m (Maybe Data)
storeInCache :: UserName -> Data -> m ()
class Monad m => DataSource m where
getFromSource :: UserName -> m (Maybe Data)
storeInSource :: UserName -> Data -> m ()
class Monad m => Logger m where
logMsg :: LogMsg -> m ()
class Monad m => ConfigProvider m where
getConfig :: m AppConfig
storeData
:: (ConfigProvider m, Cache m, DataSource m, Logger m)
=> UserName
-> Data
-> m ()
storeData userName data' = do
cfg <- getConfig
logMsg $ LogMsg $ show cfg <> " is read as config"
storeInSource userName data'
logMsg $ LogMsg $ show userName <> " is stored in database"
storeInCache userName data'
logMsg $ LogMsg $ show userName <> " is stored in cache"
requestData
:: (ConfigProvider m, Cache m, DataSource m, Logger m)
=> UserName
-> m (Maybe Data)
requestData userName = do
cfg <- getConfig
logMsg $ LogMsg $ show cfg <> " is read as config"
cached <- getFromCache userName
case cached of
Just result -> do
logMsg $ LogMsg $ show userName <> " found in cache"
return $ Just result
Nothing -> do
logMsg $ LogMsg $ show userName <> " not found in cache"
stored <- getFromSource userName
case stored of
Just result -> do
logMsg $ LogMsg $ show userName <> " found in datasource"
storeInCache userName result
return $ Just result
Nothing -> do
logMsg $ LogMsg $ show userName <> " not found in datasource"
return Nothing
---- Implementation
instance Cache IO where
getFromCache _ = return Nothing
storeInCache _ _ = return ()
instance DataSource IO where
getFromSource userName = return $ Just $ Data $ "Data: " <> show userName
storeInSource _ _ = return ()
instance Logger IO where
logMsg msg = print $ show msg
instance ConfigProvider IO where
getConfig = return $ AppConfig "HelloApp"
runApp :: IO ()
runApp = do
r <- requestData $ UserName "rohit"
print r
---- Testing
-- Default Instances
instance Logger Identity where
logMsg _ = return ()
instance ConfigProvider Identity where
getConfig = return $ AppConfig "config"
instance DataSource Identity where
getFromSource _ = return Nothing
storeInSource _ _ = return ()
instance Cache Identity where
getFromCache _ = return Nothing
storeInCache _ _ = return ()
-- Test Scenario 1: Data found in cache
newtype FoundInCache a =
FoundInCache { unInCache :: Identity a }
deriving (Show, Functor, Applicative, Monad, Logger, ConfigProvider, DataSource)
instance Cache FoundInCache where
getFromCache _ = FoundInCache $ return $ Just $ Data "Cached Data 1"
storeInCache _ _ = undefined
-- Test Scenario 2: Data not found in cache, but found in database
newtype NotFoundInCacheFoundInDatabase a =
NotFoundInCacheFoundInDatabase { unNotInCache :: Identity a }
deriving (Show, Functor, Applicative, Monad, Logger, ConfigProvider, Cache)
instance DataSource NotFoundInCacheFoundInDatabase where
getFromSource _ = return $ Just $ Data "DataSource Data 2"
storeInSource _ _ = return ()
-- Test Scenario 3: Data neither found in cache, nor found in database
newtype NeitherFoundInCacheNorDatabase a =
NeitherFoundInCacheNorDatabase { unNeitherCacheNorDatabase :: Identity a }
deriving (Show, Functor, Applicative, Monad, Logger, ConfigProvider, Cache, DataSource)
-- Test Scenario 4: Storing data in database and cache
newtype StoreData a =
StoreData { unStoreData :: Identity a }
deriving (Show, Functor, Applicative, Monad, Logger, ConfigProvider, Cache, DataSource)
spec :: IO ()
spec = hspec $ do
describe "Requesting Data" $ do
it "should get it from cache if found there" $ do
let result = unInCache (requestData $ UserName "name 1")
result `shouldBe` Identity (Just (Data "Cached Data 1"))
it "should get it from database if not found in cache" $ do
let result = unNotInCache (requestData $ UserName "name 1")
result `shouldBe` Identity (Just (Data "DataSource Data 2"))
it "should get nothing if neither found in cache nor database" $ do
let result = unNeitherCacheNorDatabase (requestData $ UserName "name 1")
result `shouldBe` Identity Nothing
describe "Storing data" $ do
it "should succeed" $ do
let result = unStoreData (storeData (UserName "name 1") (Data "data"))
result `shouldBe` Identity ()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment