generated at
Three Layer Haskell Cake
Haskellにおけるデータのレイヤリングおよびそれによる機能の分離方法について。
Problem
free monad, mtl style, monad transformerなどアプローチが色々あって選べない。
適材適所で使い分けることを前提に、それぞれを適切に織り込もう。
Solution
ReaderT Pattern
haskell
newtype AppT m a = AppT { unAppT :: ReaderT YourStuff m a } deriving (Functor, Applicative, Monad, etc)
上位レイヤがどう動くかをハンドルする。
リトライや非同期処理など、アプリケーションの中でも低レベルな部分の制御を表現する。
Pros
上位レイヤのコードをクリーンに保てそう。
Cons
低レベルな制御を扱うためテストが難しい。このレイヤにビジネスロジックを集めるとテストで苦労する。
なので何かロジックをこのレイヤーに埋める時は必ず、Input/Outputを定めて純粋な関数として保つようにする。
mtl Style
haskell
class MonadTime m where getCurrentTime :: m UTCTime
外部サービスへの依存部分を抽象化するために挿入されたレイヤ。
この例では現在の時間をimpureに取得するインスタンスとして
haskell
instance MonadTime IO where getCurrentTime :: IO UTCTime getCurrentTime = ...
のようにわかりやすいが純粋でない実装を渡すことができる一方で、pureに取得するインスタンスとして
haskell
instance MonadTime ((->) UTCTime) where getCurrentTime = id
こう書くこともできる。

更に現実的な例として

haskell
class Monad m => MonadLock m where acquireLock :: NominalDiffTime -> Key -> m (Maybe Lock) renewLock :: NominalDiffTime -> Lock -> m (Maybe Lock) releaseLock :: Lock -> m ()
これは分散ロック機構をイメージした場合だ。商用であればRedisなどとのコミュニケーションが実装になるだろう。
ユニットテスト用の環境では IORef (Map ByteString ByteString) のような型が代用できるだろう。
他にもDBとコミュニケーションするために
haskell
class (Monad m) => AcquireUser m where getUserBy :: UserQuery -> m [User] getUser :: UserId -> m (Maybe User) getUserWithDig :: UserId -> m (Maybe (User, Dog)) class AcquireUser m => UpdateUser m where deleteUser :: UserId -> m () insertUser :: User -> m ()
ユーザー User というドメインに対するアクセス手段を提供するフロントエンドだ。
バックエンドはDBかもしれないし他のWeb APIかもしれない。それはインスタンス次第で選べる。
> This layer excels at providing swappable implementations of external services.
これに尽きる。
Business Logic
単純なデータと純粋関数からなる。
> All the effectful data should have been acquired beforehand, and all effectful post-processing should be handled afterwards.
ちょうどRe-frameの概念と照応するな、と思った。
Effectful data beforehand: Coeffect
Effectful post-processing afterward: Effect
Src -> Pipe -> Sink の両端にEffectが起きるとすると、ビジネスロジックはこの1セットが複数回動作することでユースケースを実現している。
DBから結果セット fetch -> データの変換 -> KVSに書き込み ->
Web APIを呼び出して認証・認可 -> レスポンスにあるトークンを用いて別microserviceのRPC呼び出し
これらの場合 -> の両端は副作用だし、これらを組み合わせる可能性だってある。
つまりこのEffectfulな一連のステップが一つの合成単位 = 射としてビジネスロジックを構成すると言える。

Further Reading

所感
改めて見直すとレイヤリングをそれぞれ別の要素技術で実現するのがとても不思議に見えるのだった。
Snoymanは下記のような呼称を記していた。
L1: Imperative programming
L2: Object oriented programming
L3: Functional programming
若干こじつけっぽいのでもう少しいい捉え方がありそう。
L1: Infrastructure
L2: Adapter
L3: Domain
じゃないかな。
その上で実用を考えると、
Layers in Haskell Application のあたりを実現できるかどうかがポイント。ということで細かい議論はそちらのページで。