Created
August 7, 2020 07:25
-
-
Save aerohit/bb0264f751c877f5f1fc38f53c78eb9e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# 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