PHPでビゞネスロゞックを敎理する際の問題を解決するか、独自の方法で解決する

画像 こんにちは、Habr この蚘事を執筆しようずするのはこれが初めおではありたせんが、長い間、経隓を共有したいずいう芁望があり、人々は尋ねたす。



Habréには、さたざたなテクノロゞヌ、蚀語、APIなどに関する蚘事がたくさんありたすが、開発の最䞭にいるプログラマヌは、これらの目的を忘れおしたうこずがよくありたす。以䞋では、開発䞭に最も重芁なこずを忘れない方法を説明したす。



この蚘事では、さたざたなアプロヌチを組み合わせお、PHPでビゞネスロゞックを䜿甚しお䜜業を線成する方法に぀いお説明したす。



ここでは、コントロヌラヌレむダヌに沿ったサブゞェクトロゞックの拡散に関連するPHPフレヌムワヌクの問題を回避する方法に぀いお説明したす。



抂説した゜リュヌションが䜕らかの特効薬であるこずを保蚌するものではありたせん。以䞋のすべおは、䞀般的な問題を解決するためのオプションの1぀にすぎたせん。 長所ず短所の䞡方があり、このアプロヌチはその䞻なタスクに察凊したす。



䞀般的なPHPフレヌムワヌクでビゞネスロゞックを操䜜する方法に぀いお少し





通垞の状況



最も人気のあるフレヌムワヌクを芋るず、MVCアヌキテクチャパタヌンに基づいおいたす。 そのようなビゞネスロゞックを敎理するためのツヌルはありたせんが、すべおが単玔な粗悪品を䜜成するためにありたす。



ほずんどの堎合に芋られる暙準的な状況は貧匱なモデルです。たずえば、UserModelクラスは単なる属性のセットであり、ロゞックが含たれおいたせん。 ビゞネスロゞックはコントロヌラヌレむダヌに含たれおいたす。



コントロヌラは、ドメむンモデルのトランザクションスクリプトずサヌビス局の間の䜕かに倉わりたす。 入力デヌタを怜蚌し、デヌタベヌスからモデル゚ンティティを取埗し、これらの゚ンティティのビゞネスロゞックを実装したす。



これが間違っおいるず蚀っおいるのではなく、堎合によっおは実行可胜なアプロヌチです。 ビゞネスロゞックの耇雑なアヌキテクチャなしで開発する堎合



  1. シンプルなビゞネスロゞック
  2. mvpを䜜成するには
  3. プロトタむプを䜜成するずき


このような状況がない堎合は、アヌキテクチャず、このアヌキテクチャのビゞネスロゞックの堎所に぀いお考える必芁がありたす。



掗緎されたビゞネスロゞック



ビゞネスロゞックが十分に耇雑な堎合、2぀の暙準゜リュヌションがありたす。



トランザクションスクリプト



フレヌムワヌクからビゞネスロゞックを分離するには、それを個別の゚ンティティにする䟡倀がありたす。 トランザクションスクリプトを䜿甚しお、特定のナヌザヌリク゚ストを凊理する倚数のスクリプトセットを䜜成できたす。



たずえば、写真をアップロヌドする必芁がある堎合は、写真をアップロヌドするスクリプトを䜜成できたす。 プログラムで、別のクラスに割り圓おるこずができたす。



PhotoUploadScript
クラスPhotoUploadScript

{

パブリック関数run

{

/ *写真アップロヌドスクリプトの実装* /

}

}


このアプロヌチの詳现に぀いおは、「䌁業向け゜フトりェアアプリケヌションのアヌキテクチャ」ずいう本を参照しおください。すべおの長所ず短所に぀いお説明しおいたす。



ドメむンモデル



ドメむンモデルを実装するず、事態はさらに耇雑になりたす。 ビゞネスロゞックを怜蚎し、明確に分けられた責任を持぀゚ンティティを区別する必芁がありたす。 倚くの萜ずし穎がありたす。これは、サブゞェクト゚リアのファサヌドを敎理する方法ず、ビゞネス゚ンティティ間の接続が絡み合ったアプリケヌションの䜜成を回避する方法などです。



「゚ンタヌプラむズ゜フトりェアアプリケヌションのアヌキテクチャ」ずいう本では、ビゞネスロゞックのむンタヌフェむスずしお機胜し、共通の機胜UserService、OrderServiceなどでグルヌプ化された耇数のサヌビスで構成されるサヌビスレむダヌレむダヌを導入するこずが提案されおいたす。



このアプロヌチは、曞籍「䌁業向け゜フトりェアアプリケヌションのアヌキテクチャ」および私が個人的に読むこずを匷くお勧めする曞籍「Subject-Oriented DesignDDD」で詳现に怜蚎されおいたす。



私たちの物語



それがすべお始たった方法



2014幎にプロゞェクトを開始するこずが決定され、蚀語、技術、ラむブラリなどを遞択するこずで疑問が生じたした。



遞択はPHPに委ねられたしたが、既存のフレヌムワヌクの䜿甚を攟棄するこずが決定されたした。 その根拠は昔からあり、新しい装いで圌女に新しい呜を䞎えるこずにしたした。



私たちの組織では、テスト、ビゞネスロゞック、デザむンパタヌンなどの重芁性を理解しおいるため、私たちが管理するツヌルを䜜成し、PHPでビゞネスロゞックを操䜜できるようにするこずにしたした。



建築、蟞曞



基瀎は倚局アヌキテクチャを採甚したした。 非垞に䞀般的で拡倧されたスキヌムを考慮するず、3぀のレむダヌを区別できたす。







DDDの「Big Blue Book」、「Architecture of Corporate Software Applications」の䞀郚を取り䞊げ、必芁に応じおこれらすべおを凊理したした。



むンタヌフェヌス



むンタヌフェヌスは、私たちの堎合、倖界から䞻題領域ぞのアクセスを担圓する局です。

珟圚、2皮類のむンタヌフェむスがありたす。



APIは、ViewずControllerで構成されるRESTful APIです。



CLI-コマンドラむンからビゞネスロゞックを呌び出すためのむンタヌフェむスタスククラりン、キュヌのワヌカヌなど。



衚瀺する


むンタヌフェヌスレむダヌのこの郚分は非垞に単玔です。PHPプロゞェクトはAPIのみであり、ナヌザヌむンタヌフェヌスを持たないためです。



したがっお、テンプレヌト゚ンゞンでの䜜業を含める必芁はなく、jsonずしおのビュヌを提䟛したす。



必芁に応じお、この機胜を拡匵し、テンプレヌト゚ンゞンのサポヌトを远加し、さたざたな応答圢匏xmlなどを導入できたすが、ただそのようなニヌズはありたせん。 この単玔化により、アヌキテクチャのより重芁な郚分により倚くの時間を割くこずができたした。



コントロヌラヌ


コントロヌラにはドメむンロゞックを含めないでください。 コントロヌラヌは、単にルヌタヌから芁求を受信し、芁求パラメヌタヌを䜿甚しお適切なモデルむンタヌフェむスを呌び出したす。 圌はいく぀かの小さな倉換を行っおモデルず通信できたすが、ビゞネスロゞックはありたせん。



モデル



モデルレむダヌでは、アヌキテクチャの他の郚分からの分離に違反するこずなく、ほがすべおのサブゞェクト゚リアを展開できる゚ンティティの基本セットが遞択されたした。



モデルの3぀の䞻な䞀般化芁玠が特定されたした。



゚ンティティは、パラメヌタのリストの圢匏で䞀連の特性を持ち、関数の圢匏で動䜜する゚ンティティです。

コンテキストは、゚ンティティが盞互䜜甚するシナリオです。

リポゞトリは、゚ンティティを栌玍するための抜象化です。



保管



ストレヌゞレむダヌには、モデルのどの゚ンティティをデヌタベヌスに保存するか、デヌタベヌスからロヌドする方法を知っおいる䞀連のマッパヌがいたす。



詳现理論





ビュヌコントロヌラヌ



プラットフォヌムのこの郚分は、倖郚からビゞネスロゞック甚のAPIを線成したす。 これは倚くの芁玠で構成されおいたすが、理解するためにいく぀かを説明できたす。



ルヌタヌ -適切なコントロヌラヌメ゜ッドぞのhttp芁求のルヌタヌすべおが暙準です。



Viewは、本質的にValueObjetずしお枡されたモデルここでもビゞネスロゞックに関する倚くの知識をビュヌに提䟛したせんからjsonに枡されたモデルの応答のコンバヌタヌです。 埓来のMVCビュヌでは、モデルから盎接曎新を受け取りたすが、コントロヌラヌを介しお曎新を受け取りたす。



コントロヌラヌは、モデルむンタヌフェむスを隠すレむダヌです。 コントロヌラヌは、http芁求をモデルの入力パラメヌタヌに倉換し、モデルスクリプトを呌び出し、実行結果を受け取り、衚瀺甚にビュヌを返したす。



この郚分にはビゞネスロゞックは含たれおおらず、新しいAPIを䜜成する必芁がない限り、プロゞェクト䞭に倉曎されるこずはありたせん。 コントロヌラヌ局を膚匵させず、コンパクトでシンプルなたたにしたす。



モデル



次に、ビゞネスロゞックを具䜓化する最も重芁で䟡倀のある芁玠に぀いお説明したしょう。

モデルの䞻芁な芁玠である゚ンティティ、コンテキスト、およびリポゞトリをさらに詳しく考えおみたしょう。



゚ンティティ

抜象゚ンティティヌクラス
abstract class Entity { protected $_privateGetList = []; protected $_privateSetList = [ 'ctime', 'utime']; protected $id = 0; protected $ctime; protected $utime; public function getId() { return $this->id; } public function setId( $id) { $this->id = $this->id == 0? $id: $this->id; } public function getCtime() { return $this->ctime; } public function getUtime() { return $this->utime; } public function __call( $name, $arguments) { if( strpos( $name, "get" ) === 0) { $attrName = substr( $name, 3); $attrName = preg_replace_callback( "/(^[AZ])/", create_function( '$matches', 'return strtolower($matches[0]);'), $attrName); $attrName = preg_replace_callback( "/([AZ])/", create_function( '$matches', 'return \'_\'.strtolower($matches[0]);'), $attrName); if( !in_array( $attrName, $this->_privateGetList)) return $this->$attrName; } if( strpos( $name, "set" ) === 0) { $attrName = substr( $name, 3); $attrName = preg_replace_callback( "/(^[AZ])/", create_function( '$matches', 'return strtolower($matches[0]);'), $attrName); $attrName = preg_replace_callback( "/([AZ])/", create_function( '$matches', 'return \'_\'.strtolower($matches[0]);'), $attrName); if( !in_array( $attrName, $this->_privateSetList)) $this->$attrName = $arguments[0]; } } public function get( $name) { if( !in_array( $name, $this->_privateGetList)) return $this->$name; } public function set( $name, $value) { if( !in_array( $name, $this->_privateSetList)) $this->$name = $value; } static public function name() { return get_called_class(); } }
      
      







゚ンティティは、ドメむンの゚ンティティです。たずえば、ナヌザヌ、泚文、車などです。 このような゚ンティティはすべお、識別子、登録時間、䟡栌、速床などのパラメヌタヌを持぀こずができたす。 基本クラスEntityのパラメヌタヌを䜿甚しお䜜業を提䟛したす。動䜜は、開発者によっお継承クラスで既に決定されおいたす。



Entityをデヌタベヌスに保存するにはパラメヌタヌのリストも必芁ですが、モデルはこの保存に぀いお䜕も知りたせん。 むンフラストラクチャレベルでは、モデル゚ンティティをデヌタベヌスに保存する方法を知っおいるマッパヌがいたす。



プラットフォヌムのEntityの識別子は基本的なパラメヌタヌであり、同じオブゞェクトクラス内の異なる゚ンティティを䞀意に識別するこずができたす。



より詳现には、私たちの゚ンティティは、゚リック・゚ノァンスの本で説明されおいる゚ンティティに非垞に䌌おいるず蚀えたす。



Evansによるず、゜フトりェアシステム内のオブゞェクトの識別は最も重芁なタスクの1぀です。 Evansは、オブゞェクトの属性だけで識別が達成されるわけでもないこずも明確にしおいたす。



゜フトりェアシステム内のオブゞェクトを識別するこずができる特性ず芋なすこずができるもの属性、オブゞェクトのクラス、動䜜。 ゚ンティティを開発するずき、これらの特性から始めたした。



属性ぱンティティによっお異なる堎合がありたすが、ほずんどすべおの人に固有のデヌタベヌス内の識別子を識別しおいたす。 OOPず開発蚀語によっお提䟛される継承のおかげで、オブゞェクトクラスを蚭定したす。 動䜜は、各クラスのメ゜ッドによっお指定されたす。



コンテキスト



抜象クラスContext
 abstract class Context { protected $_property_list = null; function __construct( \foci\utils\PropertyList $property_list) { $this->_property_list = $property_list; } abstract public function execute(); static public function name() { return get_called_class(); } }
      
      







コンテキストは、゚ンティティが盞互䜜甚するシナリオです。 たずえば、「ナヌザヌ登録」には個別のコンテキストがあり、このコンテキストの動䜜は次のようになりたす。



  1. コンテキストを実行し、登録甚のパラメヌタヌを枡したす。 入力時に、コンテキストは単玔なパラメヌタヌint、string、textなどのリストを受け取りたす。
  2. パラメヌタの正確性の怜蚌が行われたす。 怜蚌はサブゞェクト゚リア専甚です。httpリク゚ストはここではチェックしたせん。
  3. ナヌザヌの䜜成。
  4. ナヌザヌを保存しおいたす。 これもサブゞェクト領域の䞀郚です。䞻なこずは、このナヌザヌをどこからどのように保存するかを抜象化するこずです。 ここでは、リポゞトリを䜿甚しお保存を抜象化したす。
  5. メヌル通知を送信したす。
  6. コンテキスト実行の結果を返したす。 結果を返すために、特別なクラスContextResultがありたす。これには、コンテキストの正垞な実行のサむンず、結果を含むデヌタたたぱラヌのリストが含たれたす。 View-Controllerレベルでは、モデル゚ラヌはhttp゚ラヌに倉換されたす


コンテキストはほずんど玔粋なトランザクションスクリプトですが、いく぀かの䟋倖がありたす。 Fowlerは、トランザクションスクリプトたたはドメむンモデルを通じおビゞネスロゞックを実装する䟋を提䟛したす。 ドメむンモデルを䜿甚する堎合、共通の機胜UserService、MoneyServiceなどに基づいおサヌビスが䜜成されるサヌビスレむダヌを䜿甚するこずをお勧めしたす。 この堎合、モデル゚ンティティを貧匱にしない堎合、トランザクションスクリプトは同じサヌビスレむダヌずしお機胜できたす。



たずえば、非貧匱なナヌザヌのナヌザヌ関連コンテキストUserRegContext、UserGetContext、UserChangePasswordContextなどのセットは、UserServiceサヌビスレむダヌずほが同等です。 倚くのビゞネスロゞックを取り、トランザクションスクリプトず芋なすこずができるコンテキストがありたすが、䞀郚の゚ンティティ機胜を呌び出すだけのコンテキストがあり、すべおのビゞネスロゞックがコンテキストから隠されおおり、ここではそれらはサヌビス局に近くなっおいたす。



ここから、耇雑なビゞネスロゞックの堎合、DDDシステムアヌキテクチャを玔粋な圢にするか、ビゞネスロゞックがそれほど耇雑ではなく、䜜成、ク゚リ、曎新などの暙準スクリプトで構成される堎合は、トランザクションスクリプトを䜿甚しおロゞックを敎理できたす。



リポゞトリ



汎甚リポゞトリクラス
 class Repository { function add( \foci\model\Entity &$entity) { $result = $this->_mapper->insert( $entity); return $result; } function update( \foci\model\Entity &$entity) { $result = $this->_mapper->update( $entity); return $result; } function delete( \foci\model\Entity &$entity) { $result = $this->_mapper->deleteById( $entity->getId()); return $result; } }
      
      







リポゞトリは、デヌタベヌス内の゚ンティティのストレヌゞ/削陀/曎新/怜玢を敎理するためのビゞネスロゞックの抜象化です。 具䜓的なリポゞトリは具䜓的な゚ンティティず連携したす。たずえば、UserRepositoryはナヌザヌず連携したす。



デヌタ保存



デヌタストレヌゞは、マッパヌを䜿甚しお行われたす。 ゚ンティティを操䜜するための基本的な機胜を含む䞀般化されたマッパヌクラスがありたす。



汎甚マッパヌクラス
抜象クラスマッパヌ

{

保護された$ _connection;

保護された$ _table;



関数__construct接続$接続

{

$ this-> _ connection = $ connection;

}



抜象保護関数_createEntityFromRow$行;



抜象パブリック関数挿入\ foci \ model \ Entity$ entity;

抜象パブリック関数の曎新\ foci \ model \ Entity$ entity;

抜象パブリック関数削陀\ foci \ model \ Entity$ entity;



パブリック関数getTableName

{

return $ this-> _ table;

}

}


特定の゚ンティティに察しお、この゚ンティティをデヌタベヌスに保存する方法、デヌタベヌスからロヌドする方法、削陀する方法を知っおいる独自の特定のマッパヌが䜜成されたすただし、実際にデヌタベヌスから䜕かを削陀するのは悪い習慣なので、ほずんどの堎合、削陀枈みずマヌクするだけです倉曎する。



したがっお、モデルにUserクラスがある堎合、それに応じおUserMapperクラスが䜜成されたす。 デヌタベヌスを操䜜するロゞックは別のレむダヌに配眮され、必芁に応じお簡単に眮き換えるこずができたす。



モデル゚ンティティはマッパヌに぀いおは䜕も知りたせんが、逆に、マッパヌはすべお、モデル゚ンティティに぀いお知っおいたす。



䞀般化されたスキヌム



党䜓像
画像



ここでは、プラットフォヌムの非垞に䞀般的なクラス構造に぀いお説明したすが、重芁床の䜎い小さなクラスの倚くは省略されおいたす。



すでにおわかりのように、この基本構造のみに倚くの芁玠が含たれおいたす。 特定のプロゞェクトでは、特定のビゞネスタスクを実装するコンテキストクラス、゚ンティティ、リポゞトリ、マッパヌのホスト党䜓がこれらすべおに远加されたす。



図のConfigずPropertyListは、誰もが䜿甚するナヌティリティ゚ンティティです。



少し緎習ほんの少し



理論は優れおいたすが、これをすべお実践する方法を芋おみたしょう。 財務の個人䌚蚈の申請曞を䜜成するタスクがあるずしたす。 甚語集のサンプル甚語集定矩はペむントしたせん。今のずころはすべおシンプルですお金、予算、ナヌザヌ、補品、サヌビス、カレンダヌ



次に、明確にするために、いく぀かのナヌザヌシナリオを䜜成し、非暙準のものを䜿甚したす。 商品の賌入-私たちの賌入は、単にお金を䞀気に取り消すずいう事実であるず考えたす。 カレンダヌに埓っおサヌビスの支払いを蚭定したす-これは、説明付きの定期的なお金の償华です。



゚ンティティを遞択したすMoney、Budget、User、Product、Service、Calendar。

ナヌザヌシナリオには3぀のコンテキストがありたす。



最初のコンテキストは、単に補品を賌入するこずです。



BuyOrderContex



  1. 入力たずえば、トヌクンによっおデヌタベヌスからナヌザヌを取埗したす
  2. 新しい補品を取埗たたは䜜成したす
  3. 指定した金額の補品賌入をセットアップするようにBudgetに指瀺したす。


残りはより耇雑で、2番目のナヌザヌシナリオは2぀のコンテキストに分割されたす。最初のシナリオではお金をい぀償华するかを蚭定し、2番目のシナリオでは償华を行いたす。



SetSchedulePayForServiceContext



  1. 入力たずえば、トヌクンによっおデヌタベヌスからナヌザヌを取埗したす
  2. 新しいサヌビスを取埗たたは䜜成したす
  3. 指定された日付のサヌビスサヌビスのお金を匕き萜ずすカレンダヌにむンストヌルしたす


SchedulePayForServiceContext



  1. 珟圚サヌビスに料金があるかどうかをカレンダヌで確認したす
  2. お金を償华する必芁があるサヌビスをロヌドしたす
  3. サヌビス料金を請求したす


すでにこの小さな䟋では、このアプロヌチの長所ず短所もいく぀か芋られたすたずえば、異なるコンテキストでのロゞックの耇補。これは、本「䌁業゜フトりェアアプリケヌションのアヌキテクチャ」によく曞かれおいたす。



おわりに



私たちの実践



機胜分離



アプリケヌション党䜓をコンテキストに分割するず、異なるサヌバヌに簡単に配垃できたす。

たずえば、ナヌザヌ登録に関連付けられたコンテキストがあるため、このグルヌプ党䜓を簡単に取埗しお、他のアプリケヌションを䞭断するこずなく別のサヌバヌに転送できたす。



コンテキストがコンテキストを呌び出したす



コンテキストはコンテキストを匕き起こす可胜性があるため、耇雑なチェヌンを構築できたす。 この手法はほずんど䜿甚されたせん。 たた、このアプロヌチには倚くの萜ずし穎があり、そのような䜿甚を犁止する蚈画がありたす。



クラりンタスク



実行の単䜍ずしおのコンテキストにより、どこからでも呌び出すこずができたす。 タスクの冠ずしおコンテキストを起動するロゞックは、これに基づいおいたす。



SPA



私たちのプロゞェクトの1぀は、クラむアント郚分がJavaScriptで完党に蚘述され、RESTfull APIを介しおPHPのサヌバヌ郚分ずやり取りするサむトです。 開発を開始したずき、これを蚈画するこずさえしたせんでしたが、サヌバヌずしおのプラットフォヌムでSPAアプリケヌションを構築するこの機䌚は非垞に䟿利であるこずが刀明したした。



テスト䞭



すべおのコヌドはテストでカバヌされたす。 次の2皮類のテストを䜿甚したす。



  1. Configなどのクラスのナニットテスト
  2. APIぞのリク゚ストに察する受け入れテスト。


蚈画



  1. 私たちは、開発のより広範な普及を非垞に欠いおいたす。 すべおは内郚䜿甚専甚に曞かれおおり、これには欠点がありたす。
  2. 負荷の高いプロゞェクトには十分な䟵入がありたせん。 すでにいく぀かの実甚的な商甚プロゞェクトがありたすが、それらを高負荷ず呌ぶこずはできたせん。
  3. これたでのトランザクションを凊理するロゞックは、あたり考慮されおいたせん。 この時点で、珟圚、カプセル化モデルにわずかな違反がありたす。 将来的には、デヌタベヌストランザクションから抜象化する䜜業単䜍を導入する予定です。
  4. テストは䞻にAPI、コンテキストテストを行う蚈画をカバヌしおいたす。 これにより、サブゞェクト領域を個別にテストできたす。


結果ずしお䜕を埗たのか



  1. むンフラストラクチャからビゞネスロゞックを明確に分離しお、アプリケヌションを䜜成するための柔軟なプラットフォヌム。
  2. 私たちの開発によっお完党に制埡され、これは論争の的ずなっおいる問題の技術的問題を解決する䞊で倚くの利点をもたらしたす。
  3. 新しい開発者がプロ​​ゞェクトにすばやく参加できるようにする、よく知られたデザむンパタヌンに基づくアヌキテクチャ。
  4. 新しいプロゞェクトでは忘れられがちで、既存のプロゞェクトでの䜿甚を暡玢しない、実蚌枈みの蚭蚈アプロヌチを適甚した豊富な経隓。


結論



䞀般的に、完了した䜜業の結果に満足しおいたす。 曞かれたプラットフォヌムは私たちの問題を解決したす。 これにより、ビゞネスロゞックを柔軟に実装でき、開発が倧幅に簡玠化されたす。 改善ず拡匵のための倚くの蚈画がありたす。



PSPHPフレヌムワヌクのサブゞェクト領域の問題を解決するためのこのようなアプロヌチがコミュニティにずっお興味深いものである堎合、オヌプン゜ヌスを準備しお準備するこずができたす。



PSSすぐにすべおがすでにあるのに、なぜ自転車が必芁なのかずいうフレヌズを予芋したす。 これに぀いお議論する理由はないず思いたす。これが私たちのアプロヌチであり、うたくいきたした。



PSS近芪盞姊のトランザクションスクリプトずドメむンモデル、そしおなぜビゞネス䞊の問題を解決するための柔軟なツヌルを䜜っお入手しないのかずいう疑問も予芋したす。



All Articles