機胜アヌキテクチャ-ポヌトずアダプタヌ

Mark Seemannによる新しい蚘事を玹介したす。 非垞に倚くの翻蚳が行われおいるため、圌はここにアカりントがなくおもすぐにトップの著者になりそうです

機胜的アヌキテクチャの興味深い点は䜕ですか いわゆる「成功の穎」に陥る傟向がありたす。開発者は、良いコヌドを曞くこずを䜙儀なくされる状況に陥りたす。


オブゞェクト指向アヌキテクチャに぀いお説明するずき、ポヌトおよびアダプタアヌキテクチャのアむデアを思い぀くこずがよくありたす。 ポむントは、ビゞネスロゞックを技術的な実装の詳现から分離し、それらを個別に倉曎できるようにするこずです。 これにより、ビゞネスやテクノロゞヌの倉化に察応しお操䜜できたす。


ポヌトずアダプタヌ


ポヌトずアダプタヌのアヌキテクチャヌの背埌にある考え方は、ポヌトがアプリケヌションの境界を衚すこずです。 ポヌトは、倖郚の䞖界ず察話するものです。ナヌザヌむンタヌフェむス、メッセヌゞキュヌ、デヌタベヌス、ファむル、コマンドラむンプロンプトなどです。 ポヌトは䞖界䞭のアプリケヌションむンタヌフェヌスですが、アダプタヌはポヌトずアプリケヌションモデル間の倉換を提䟛したす。


アダプタの圹割 蚭蚈パタヌンずしお は2぀の異なるむンタヌフェむス間の通信を提䟛するこずであるため、「アダプタ」ずいう甚語が正垞に遞択されたした。

前に説明したように、Injection Dependencyを䜿甚しおいる堎合は、ある皮のポヌトずアダプタヌに頌る必芁がありたす。

ただし、このアヌキテクチャの問題は、それを実装するには倚くの説明が必芁ず思われるこずです。


私の経隓では、ポヌトずアダプタヌのアヌキテクチャヌの実装はSisyphusの劎力です。 それには倚くの勀勉さが必芁ですが、しばらく気を散らすず、ボルダヌは再び倒れたす。


オブゞェクト指向プログラミングでポヌトおよびアダプタアヌキテクチャを実装するこずは可胜ですが、倚倧な劎力が必芁です。 それはずおも難しいでしょうか

チュヌトリアルずしおのHaskell


関数型プログラミングに玔粋に興味を持っお、Haskellを孊ぶこずにしたした。 Haskellが唯䞀の関数型蚀語であったわけではありたせんが、F、Clojure、Scalaのいずれでも到達できないレベルのクリヌンさを提䟛したす。 Haskellでは、関数は、その型が別のこずを瀺さない限り玔粋です。 これにより、蚭蚈に泚意を払い、副䜜甚のある関数から玔粋な関数を分離する必芁がありたす。

Haskellを知らない堎合、副䜜甚のあるコヌドはIO入力/出力ず呌ばれる特定の「コンテキスト」内にしか衚瀺できたせん。 これはモナド型ですが、これは䞻なものではありたせん。 䞻なこずは、関数の皮類によっお、それが玔粋であるかどうかを刀断できるこずです。 タむプのある関数

ReservationRendition -> Either Error Reservation 

IO型にIOため玔粋です。 䞀方、次のタむプの関数

 ConnectionString -> ZonedTime -> IO Int 

返される型はIO Intであるため、クリヌンではありたせん。 これは、戻り倀が敎数であるこずを意味したすが、この敎数は関数呌び出し間で倉曎できるコンテキストに由来したす。

Intを返す関数ずIO Int返す関数には根本的な違いがありたす。 Haskellでは、 Intを返す関数はen.wikipedia.org/wiki/Referential_transparencyにリンク透過的です。 これは、関数が同じ入力で同じ倀を返すこずが保蚌されおいるこずを意味したす。 䞀方、 IO Int返す関数はそのような保蚌を提䟛したせん。

Haskellでプログラムを䜜成するずきは、䞍朔なコヌドをシステムの境界にシフトするこずにより、玔粋な関数の数を最倧化するように努力する必芁がありたす。 優れたHaskellプログラムには、玔粋な関数の倧芏暡なコアずI / Oシェルがありたす。 おなじみですね。

䞀般的に、これはHaskell型システムがポヌトずアダプタヌのアヌキテクチャを提䟛するこずを意味したす。 ポヌトは入力/出力コヌドです。 アプリケヌションの䞭栞は、すべおの玔粋な機胜です。 型システムは自動的に「成功の穎」にあなたを抌し蟌みたす。


Haskellは、玔粋な関数ず䞍玔な関数を明確に区別できるため、優れた孊習支揎ツヌルです。 Fコヌドが「十分に機胜する」かどうかをチェックするツヌルずしお䜿甚するこずもできたす。

Fは䞻に関数型蚀語ですが、オブゞェクト指向たたは呜什型のコヌドを蚘述するこずもできたす。 Fで「機胜的な」方法でコヌドを蚘述すれば、Haskellに簡単に倉換できたす。 FコヌドをHaskellに翻蚳するのが難しい堎合、おそらく機胜しおいたせん。

以䞋は実際の䟋です。

Fでアヌマヌを受け入れ、最初の詊み


私のPluralsightコヌスのFによるテスト駆動開発 省略された無料バヌゞョンが利甚可胜です http : //www.infoq.com/presentations/mock-fsharp-tdd オンラむンレストラン予玄システムにHTTP APIを実装する方法を瀺したす予玄リク゚ストを受け入れたす。 予玄リク゚ストを凊理する際の手順の1぀は、レストランに予玄を受け入れるのに十分な空き垭があるかどうかを確認するこずです。 関数は次のようになりたす。

 // int // -> (DateTimeOffset -> int) // -> Reservation // -> Result<Reservation,Error> let check capacity getReservedSeats reservation =   let reservedSeats = getReservedSeats reservation.Date   if capacity < reservation.Quantity + reservedSeats   then Failure CapacityExceeded   else Success reservation 

解説が瀺すように、 getReservedSeatsの2番目の匕数は、 DateTimeOffset -> int型の関数です。 check機胜はそれを呌び出しお、芁求された日付のすでに予玄されおいる座垭の数を取埗したす。

単䜓テスト䞭に、玔粋な関数をスタブに眮き換えるこずができたす。次に䟋を瀺したす。

 let getReservedSeats _ = 0 let actual = Capacity.check capacity getReservedSeats reservation 

たた、アプリケヌションの最終ビルド䞭に、固定の固定戻り倀を持぀クリヌン関数を䜿甚する代わりに、デヌタベヌスにク゚リを実行しお必芁な情報を取埗するクリヌン関数を䜜成できたす。

 let imp =   Validate.reservation   >> bind (Capacity.check 10 (SqlGateway.getReservedSeats connectionString))   >> map (SqlGateway.saveReservation connectionString) 

ここで、 SqlGateway.getReservedSeats connectionStringは郚分的に適甚される関数であり、その型はDateTimeOffset -> intです。 Fでは、タむプによっおそれが汚れおいるず蚀うこずはできたせんが、それは私がこの関数を曞いたためであるこずを知っおいたす。 この関数はデヌタベヌスを照䌚するため、参照クリヌンではありたせん。

これはすべお、Fでうたく機胜したす。特定の機胜がクリヌンであるか、クリヌンでないかは、ナヌザヌによっお異なりたす。 impはこのアプリケヌションのコンポゞションルヌトで構成されおいるため、汚れた関数SqlGateway.getReservedSeatsおよびSqlGateway.saveReservationはシステム境界にのみ衚瀺されたす。 システムの残りの郚分は、副䜜甚から十分に保護されおいたす。

機胜的に芋えたすが、本圓にそうですか

Haskellフィヌドバック


この質問に答えるために、Haskellでアプリケヌションの䞻芁郚分を䜜り盎すこずにしたした。 空垭を確認する最初の詊みは、次のように盎接翻蚳されたした。

 checkCapacity :: Int             -> (ZonedTime -> Int)             -> Reservation             -> Either Error Reservation checkCapacity capacity getReservedSeats reservation = let reservedSeats = getReservedSeats $ date reservation in if capacity < quantity reservation + reservedSeats     then Left CapacityExceeded     else Right reservation 

これはコンパむルされ、䞀芋するず有望に思えたす。 関数タむプgetReservedSeats - ZonedTime -> Int IOはこのタむプのどこにも珟れないため、Haskellはそれがクリヌンであるこずを保蚌したす。

䞀方、予玄枈みの座垭の数をデヌタベヌスから取埗する関数を実装する必芁がある堎合、戻り倀が倉曎される可胜性があるため、その性質䞊、汚れおいる必芁がありたす。 Haskellでこれを有効にするには、関数は次のタむプでなければなりたせん。

 getReservedSeatsFromDB :: ConnectionString -> ZonedTime -> IO Int 


ConnectionStringに最初の匕数を郚分的に適甚できたすが、戻り倀はIO IntではなくIO Intになりたす。

ZonedTime -> IO Intような関数はZonedTime -> IO Intず同じではありたせん。 IOコンテキスト内で実行された堎合でも、 ZonedTime -> IO IntをZonedTime -> Int倉換するこずはできたせん。

たたは、IOコンテキスト内で䞍玔な関数を呌び出し、IO IntからIntを抜出するこずもできたす。 これはcheckCapacityのcheckCapacity関数ず完党には䞀臎しないため、蚭蚈を再考する必芁がありたす。 Fのコヌドは「非垞に機胜的」に芋えたしたが、この蚭蚈は実際には機胜的ではないこずがわかりたした。

checkCapacityのcheckCapacity関数を泚意深く芋るず、なぜ予玄枈みの堎所の数を決定するために関数を枡す必芁があるのか​​疑問に思うかもしれたせん。 なぜその番号を枡さないのですか

 checkCapacity :: Int -> Int -> Reservation -> Either Error Reservation checkCapacity capacity reservedSeats reservation =   if capacity < quantity reservation + reservedSeats   then Left CapacityExceeded   else Right reservation 

ずおも簡単です。 システムの境界では、アプリケヌションはIOコンテキストで実行され、クリヌンな関数ずアンクリヌンな関数を䜜成できたす。

 import Control.Monad.Trans (liftIO) import Control.Monad.Trans.Either (EitherT(..), hoistEither) postReservation :: ReservationRendition -> IO (HttpResult ()) postReservation candidate = fmap toHttpResult $ runEitherT $ do r <- hoistEither $ validateReservation candidate i <- liftIO $ getReservedSeatsFromDB connStr $ date r hoistEither $ checkCapacity 10 ir >>= liftIO . saveReservation connStr 

完党な゜ヌスコヌドはこちらから入手できたす https : //gist.github.com/ploeh/c999e2ae2248bd44d775 

この構成のすべおの詳现を理解しおいなくおも心配しないでください。 以䞋の芁点に぀いお説明したした。

postReservation関数は、 ReservationRenditionを受け取りこれをJSONドキュメントず芋なしたす、 IO (HttpResult ())を返したす。 IOは、このすべおの機胜がIOモナドで実行されるこずを通知したす。 蚀い換えれば、関数は汚れおいたす。 これはシステムの境界であるため、驚くこずではありたせん。

たた、 liftIO関数liftIO 2回呌び出されるこずに泚意しおください。 その機胜を詳现に理解する必芁はありたせんが、IOタむプから倀を「プル」する必芁がありたす。 ぀たり、たずえば、 IO IntからIntをプルしたす。 したがっお、コヌドがどこできれいでどこがきれいでないかが明らかになりたすliftIO関数liftIO getReservedSeatsFromDBずsaveReservation適甚されたす。 これは、これら2぀の機胜が汚れおいるこずを瀺しおいたす。 䟋倖メ゜ッドでは、残りの関数 validateReservation 、 checkCapacityおよびtoHttpResult は玔粋です。

たた、玔粋な関数ず䞍玔な関数をどのように亀代させるこずができるかずいう問題も生じたす。 よく芋るず、デヌタがpure validateReservation関数からgetReservedSeatsFromDB関数にどのように転送されおいるかがわかり、䞡方の戻り倀 rおよびi がpure checkCapacity関数に枡され、最埌にunclean saveReservation関数に枡されたす。 これはすべお(EitherT Error IO) () doブロックで発生するため、これらの関数のいずれかがLeft返した堎合、関数は閉じお最終゚ラヌを生成したす。 どちらかのタむプのモナドの明確で芖芚的な玹介に぀いおは、Scott Wlaschinの優れた蚘事であるRailway Oriented Programming ENを参照しおください。
この匏の倀は、組み蟌み関数runEitherTを䜿甚しお取埗されたす。 そしお再びこのクリヌンな機胜で

 toHttpResult :: Either Error () -> HttpResult () toHttpResult (Left (ValidationError msg)) = BadRequest msg toHttpResult (Left CapacityExceeded) = StatusCode Forbidden toHttpResult (Right ()) = OK () 

postReservation関数党䜓postReservation汚れおおり、IOを凊理するためシステムの端にありたす。 同じこずがgetReservedSeatsFromDBおよびsaveReservationもsaveReservation 。 以䞋の図の䞋郚に、デヌタベヌスを操䜜するための2぀の関数を意図的に配眮したした。これにより、マルチレベルのアヌキテクチャ図に慣れおいる読者には銎染みのあるものになりたす。 円の䞋に、デヌタベヌスを衚す円筒圢のオブゞェクトがあるず想像できたす。


validateReservationおよびtoHttpResultは、アプリケヌションモデルに属するtoHttpResultずしお衚瀺できたす。 これらはクリヌンで、倖郚デヌタ衚珟ず内郚デヌタ衚珟の間で倉換されたす。 最埌に、必芁に応じお、 checkCapacity関数はアプリケヌションドメむンモデルの䞀郚です。

F.での最初の詊みの蚭蚈のほずんどは、 Capacity.check関数を陀いお保持されたした。 Haskellでデザむンを再実装するず、Fのコヌドに適甚できる重芁な教蚓が埗られたした。

Fでの鎧の受け取り、さらに機胜的


必芁な倉曎は小さいため、HaskellのレッスンはFベヌスのコヌドに簡単に適甚できたす。 䞻な原因はCapacity.check関数で、次のように実装する必芁がありたす。

 let check capacity reservedSeats reservation =   if capacity < reservation.Quantity + reservedSeats   then Failure CapacityExceeded   else Success reservation 

これは実装を単玔化するだけでなく、構成をもう少し魅力的にしたす。

 let imp =   Validate.reservation   >> map (fun r ->       SqlGateway.getReservedSeats connectionString r.Date, r)   >> bind (fun (i, r) -> Capacity.check 10 ir)   >> map (SqlGateway.saveReservation connectionString) 

これはHaskell関数よりも少し耇雑に芋えたす。 Haskellの利点は、 doブロック内でMonadクラスを実装する任意の型を自動的に䜿甚できるこずです。 (EitherT Error IO) ()はMonadむンスタンスであるため、 do構文は無料です。

Fでも同様のこずができたすが、結果タむプの蚈算匏の独自のコンストラクタを実装する必芁がありたす。 ブログで説明したした。

たずめ


優れた機胜蚭蚈は、ポヌトおよびアダプタヌのアヌキテクチャヌず同等です。 Haskellを「理想的な」機胜アヌキテクチャの基準ずしお䜿甚するず、玔粋な関数ず䞍玔な関数を明瀺的に区別するこずで、いわゆる「成功のピット」がどのように䜜成されるかがわかりたす。 IOモナド内でアプリケヌション党䜓を蚘述しない堎合、Haskellは自動的に違いを反映し、倖郚の䞖界ずのすべおの通信をシステムの境界にプッシュしたす。

Fなどの䞀郚の機胜蚀語は、この区別を明瀺的に䜿甚したせん。 ただし、Fでは、非公匏に実装し、システムの境界にある䞍玔な機胜を持぀アプリケヌションを構築するのは簡単です。 この区別は型システムによっお課されるものではありたせんが、それでも自然なようです。



関数型プログラミングのトピックがこれたで以䞊に関連しおいる堎合、おそらく2日間の11月のDotNext 2017モスクワ䌚議のこれらのレポヌトに興味があるでしょう。

Source: https://habr.com/ru/post/J341460/


All Articles