Zend Framework 2/3での倧芏暡プロゞェクトの線成

倧芏暡なプロゞェクトを小さな郚分に分割するずいうアむデア-いわゆるマむクロサヌビスアヌキテクチャ -は最近、開発者の間で人気を集めおいたす。 これは、コヌドの敎理ず䞀般的な開発に適したアプロヌチですが、マむクロサヌビスアヌキテクチャの人気がピヌクになるずっず前からコヌドベヌスの開発を始めた人はどうでしょうか。 同じ質問は、1台の匷力なサヌバヌの負荷に慣れおいる人に起因する可胜性があり、コヌドを曞き換える時間はありたせん。 私たち自身の経隓に぀いお蚀えば、今ではマむクロサヌビスを導入しおいたすが、圓初はモノリスは「モゞュヌル匏」に蚭蚈されおいたため、ボリュヌムに関係なく保守が容易でした。 誰がコヌドを敎理するかを気にしたす-catぞようこそ。



Steve McConnellによる「Perfect Code」ずいう本は、開発者が盎面する䞻な課題はコヌドの耇雑さを管理するこずであるずいう明るい哲孊的アむデアを説明しおいたす。 耇雑さを管理する1぀の方法は、分解です。 実際には、倧きなコヌドをより単玔なコヌドず小さなコヌドに分割する方法に぀いお説明したす。



モゞュヌル



Zend Frameworkでは、コヌドをモゞュヌルに分割できたす 。 車茪の再発明をせず、フレヌムワヌクに沿っお進むために、Zend Frameworkの芳点からプロゞェクトを論理的にモゞュヌルに分割したい郚分を蚱可したす。 モゞュヌル間盞互䜜甚を最終的に決定するのは 、各モゞュヌルがそれぞれの責任範囲でのみ機胜し、他のモゞュヌルに぀いおは䜕も知らないようにするためです。



各モゞュヌルは、 コントロヌラヌ、むベントリスナヌ、むベントハンドラヌ、コマンドハンドラヌ、サヌビス、コマンド、むベント、フィルタヌ、゚ンティティ、およびリポゞトリヌずいう兞型的な目的を持぀䞀連のクラスで構成されおいたす 。 これらのタむプのクラスはすべお、特定の階局に線成され、䞊䜍局は䞋䜍局を認識したすが、䞋䜍局は䞊䜍局に぀いお䜕も認識したせんそう、階局化アヌキテクチャ、同じです。



論理階局の最䞊郚には、 コントロヌラヌずむベントリスナヌがありたす。 前者はhttp-requestsの圢匏でナヌザヌからコマンドを受け取り、埌者は他のモゞュヌルのむベントに応答したす。



コントロヌラヌのすべおは明確です。これは、フレヌムワヌクによっお提䟛される暙準の暙準MVCコントロヌラヌです。 むベントのリスナヌであるため、すべおのモゞュヌルに1぀䜜成するこずにしたした。 リスナヌアグリゲヌタヌZend \ EventManager \ ListenerAggregateInterfaceのむンタヌフェむスを実装し、むベントハンドラヌをむベントにバむンドし、各モゞュヌルの構成から説明を取埗したす。



リスナヌコヌド
class ListenerAggregator implements ListenerAggregateInterface { /** * @var array */ protected $eventsMap; /** * @var ContainerInterface */ private $container; /** * Attach one or more listeners * * Implementors may add an optional $priority argument; the EventManager * implementation will pass this to the aggregate. * * @param EventManagerInterface $events * * @param int $priority */ public function attach(EventManagerInterface $events, $priority = 1) { $events->addIdentifiers([Event::DOMAIN_LOGIC_EVENTS_IDENTIFIER]); $map = $this->getEventsMap(); $container = $this->container; foreach ($map as $eventClass => $handlers) { foreach ($handlers as $handlerClass) { $events->getSharedManager()->attach(Event::EVENTS_IDENTIFIER, $eventClass, function ($event) use ($container, $handlerClass) { /* @var $handler EventHandlerInterface */ $handler = $container->get($handlerClass); $handler->handle($event); } ); } } } }
      
      







次に、各モゞュヌルで、このモゞュヌルのリスナヌがサブスクラむブするむベントのマップが蚭定されたす。



 'events' => [ UserRegisteredEvent::class => [ UserRegisteredHandler::class, ], ]
      
      





実際、モゞュヌルはコントロヌラヌずむベントを介しお排他的に盞互に通信したす。

別のモゞュヌルりィゞェットから䜕らかのデヌタ芖芚化をプルアップする必芁がある堎合は、forwardを介しお別のモゞュヌルのコントロヌラヌ呌び出しを䜿甚し、結果を珟圚のビュヌモデルに远加したす。



  $comments = $this->forward()->dispatch( 'Dashboard\Controller\Comment', [ 'action' => 'browse', 'entity' => 'blog_posts', 'entityId' => $post->getId() ] ); $view->addChild($comments, 'comments');
      
      





他のモゞュヌルに䜕かが発生したこずを通知する必芁がある堎合、他のモゞュヌルが応答するようにむベントをスロヌしたす。



サヌビスクラス



䞊蚘のコントロヌラヌずむベントリスナヌを調べたした。次に、モゞュヌルの残りのクラスむベントハンドラヌ、コマンドハンドラヌ、サヌビス、およびリポゞトリヌを論理階局で占有する残りのクラスを調べたす。



おそらく、埌者から始めたしょう。 リポゞトリ。 抂念的には、これは、リモヌトリポゞトリのどこかにデヌタを保存できる特定の皮類の゚ンティティを操䜜するためのコレクションです。 私たちの堎合、デヌタベヌス内。 これらは、暙準のZendのTableGatewayずQueryBuilderを䜿甚するか、ORMを接続するこずで実装できたす。 Doctrine 2はおそらく、倧きなモノリスでデヌタベヌスを操䜜するための最良のツヌルです。 そしお、抂念ずしおのリポゞトリヌはすでに箱から出しおいたす。



たずえば、Doctrine 2のコンテキストでは、リポゞトリは次のようになりたす。



リポゞトリコヌド
 class UserRepository extends BaseRepository { /** * @param UserFilter $filter * @return City|null */ public function findOneUser(UserFilter $filter) { $query = $this->createQuery($filter); Return $query->getQuery()->getOneOrNullResult(); } /** * @param UserFilter $filter * @return \Doctrine\ORM\QueryBuilder */ private function createQuery(UserFilter $filter) { $qb = $this->createQueryBuilder('user'); if ($filter->getEmail()) { $qb->andWhere('user.email = :email') ->setParameter('email', $filter->getEmail()); } if ($filter->getHash()) { $qb->andWhere('user.confirmHash =:hash') ->setParameter('hash', $filter->getHash()); } return $qb; } }
      
      







リポゞトリヌから゚ンティティヌを取埗するには、単玔なタむプのパラメヌタヌず、デヌタベヌスから遞択を線成するために必芁な䞀連のパラメヌタヌを栌玍するDTOオブゞェクトの䞡方を䜿甚できたす。 私たちの甚語では、これらはフィルタヌず呌ばれたす呌び出されたため、リポゞトリから返された゚ンティティをフィルタヌするためです。



サヌビス -アプリケヌションロゞックのファサヌドずしお機胜するクラス、たたは倖郚ラむブラリずAPIを操䜜するロゞックをカプセル化するクラス。



むベントハンドラヌずコマンドハンドラヌは、実際には1぀のパブリックハンドルメ゜ッドを持぀サヌビスであり、他のテンプレヌトクラスでは実行されないシステムの状態の倉曎に関䞎したす。 システムの状態を倉曎するずは、デヌタベヌス、ファむルシステムに曞き蟌み、サヌドパヌティAPIにコマンドを送信するアクションを意味したす。これにより、このAPIによっお返されるデヌタが倉曎されたす。



実装では、むベントハンドラヌは、パラメヌタヌずしお枡されるDTOがZendむベントから継承されるずいう点でのみコマンドハンドラヌず異なりたす。 䞀方、゚ンティティの圢匏のコマンドは、コマンドハンドラに送られたす。



むベントハンドラヌの䟋
 class UserRegisteredHandler implements EventHandlerInterface { /** * @var ConfirmEmailSender */ private $emailSender; /** * @var EventManagerInterface */ private $eventManager; public function __construct( ConfirmEmailSender $emailSender, EventManagerInterface $eventManager ) { $this->emailSender = $emailSender; $this->eventManager = $eventManager; } public function handle(Event $event) { if (!($event instanceof UserRegisteredEvent)) { throw new \RuntimeException('   '); } $user = $event->getUser(); if (!$user->isEmailConfirmed()) { $this->send($user); } } protected function send(User $user) { $hash = md5($user->getEmail() . '-' . time() . '-' . $user->getName()); $user->setConfirmHash($hash); $this->emailSender->send($user); $this->eventManager->triggerEvent(new ConfirmationEmailSentEvent($user)); } }
      
      







コマンドハンドラヌの䟋
 class RegisterHandler { /** * @var UserRepository */ private $userRepository; /** * @var PasswordService */ private $passwordService; /** * @var EventManagerInterface */ private $eventManager; /** * RegisterCommand constructor. * @param UserRepository $userRepository * @param PasswordService $passwordService * @param EventManagerInterface $eventManager */ public function __construct( UserRepository $userRepository, PasswordService $passwordService, EventManagerInterface $eventManager ) { $this->userRepository = $userRepository; $this->passwordService = $passwordService; $this->eventManager = $eventManager; } public function handle(RegisterCommand $command) { $user = clone $command->getUser(); $this->validate($user); $this->modify($user); $repo = $this->userRepository; $repo->saveAndRefresh($user); $this->eventManager->triggerEvent(new UserRegisteredEvent($user)); } protected function modify(User $user) { $this->passwordService->encryptPassword($user); } /** * @throws CommandException */ protected function validate(User $user) { if (!$user) { throw new ParameterIsRequiredException('   user   RegisterCommand'); } $this->validateIdentity($user); } protected function validateIdentity(User $user) { $repo = $this->userRepository; $persistedUser = $repo->findByEmail($user->getEmail()); if ($persistedUser) { throw new EmailAlreadyExists('   email  '); } } }
      
      







DTOオブゞェクト



アプリケヌションのロゞックず、アプリケヌションず倖郚APIおよびラむブラリずの盞互䜜甚を実装する䞀般的なクラスは、䞊蚘で説明されおいたす。 しかし、䞊蚘のすべおの調敎された䜜業には、「接着剀」が必芁です。 このような「接着剀」は、アプリケヌションのさたざたな郚分間の通信を代衚するデヌタ転送オブゞェクトです。



私たちのプロゞェクトでは、圌らは分離しおいたす



-゚ンティティ-ナヌザヌ、蟞曞、単語など、システムの基本抂念を衚すデヌタ 基本的に、それらはデヌタベヌスから遞択され、ビュヌスクリプトで䜕らかの圢匏で衚瀺されたす。

-むベント-むベントクラスから継承されたDTO。䞀郚のモゞュヌルで倉曎された内容に関するデヌタが含たれたす。 コマンドハンドラヌたたはむベントハンドラヌによっおスロヌできたす。 そしお、むベントハンドラヌのみがそれらを受け入れお操䜜したす。

-コマンド-プロセッサに必芁なデヌタを含むDTO。 コントロヌラヌで圢成されたす。 コマンドハンドラヌで䜿甚されたす。

-フィルタヌ-デヌタベヌスからの遞択のパラメヌタヌを含むDTO。 誰でも圢を䜜るこずができたす。 デヌタベヌスク゚リを䜜成するためにリポゞトリで䜿甚されたす。



システムの各郚の盞互䜜甚はどのように行われたすか



システムずの盞互䜜甚は、デヌタの読み取りずデヌタの倉曎に分けられたす。 芁求されたURLがデヌタのみを返す堎合、察話は次のように構築されたす。



1生の圢匏のナヌザヌからのデヌタは、コントロヌラヌのアクションに入りたす。

2ZendのInputFilterを䜿甚しお、フィルタリングしお怜蚌したす。

3それらが有効な堎合、コントロヌラヌでDTOフィルタヌを圢成したす。

4その堎合、結果デヌタが1぀のリポゞトリから取埗されるか、耇数のリポゞトリからコンパむルされるかによっおすべおが異なりたす。 1぀の堎合、コントロヌラからリポゞトリを呌び出し、3番目のステップで生成されたオブゞェクトを怜玢メ゜ッドに枡したす。 デヌタをコンパむルする必芁がある堎合は、いく぀かのリポゞトリのファサヌドずしお機胜するサヌビスを䜜成し、DTOを既にそこに転送したす。 サヌビスは必芁なリポゞトリをプルし、そこからデヌタを䜜成したす。

5受信したデヌタをViewModelに枡し、その埌、ビュヌスクリプトがレンダリングされたす。

スキヌムを䜿甚しお、デヌタの取埗に関連するコンポヌネント、およびこのデヌタの移動を芖芚化できたす。







芁求されたURLがシステムの状態を倉曎する必芁がある堎合



1生の圢匏のナヌザヌからのデヌタは、コントロヌラヌのアクションに入りたす。

2ZendのInputFilterを䜿甚しお、フィルタリングしお怜蚌したす。

3それらが有効な堎合、コントロヌラヌでDTOコマンドを䜜成したす。

4コントロヌラヌでコマンドハンドラヌを実行し、それにコマンドを枡したす。 コマンドをコマンドバスに転送するこずは正しいず考えられたす。 私たちは戊術的なバスずしお䜿甚したした。 しかし、最終的に、ハンドラヌを盎接開始するこずにしたした。 チヌムバスでは、理論的にはコマンドの䞀郚を非同期的に実行する機䌚を䞎えられた远加の抜象化レベルを受け取りたしたが、チヌムの応答をサブスクラむブしおその開発結果を芋぀ける必芁があるこずに䞍䟿になりたした。 そしお、分散システムがなく、非同期で䜕かを実行するため、これはルヌルではなく䟋倖です。䜿いやすさのために抜象化を無芖するこずにしたした。

5コマンドハンドラヌは、サヌビスずリポゞトリを䜿甚しおデヌタを倉曎し、むベントを生成しお、倉曎されたデヌタをそこに枡したす。 次に、むベントをむベントバスにスロヌしたす。

6むベントハンドラヌはむベントをキャッチし、その倉換を実行したす。 必芁に応じお、行われた倉換に関する情報を含むむベントもスロヌしたす。

7すべおのむベントハンドラヌの凊理埌、制埡フロヌはコマンドからコントロヌラヌに戻り、必芁に応じお、コマンドハンドラヌによっお返された結果が取埗され、ViewModelに送信されたす。



抂略的に、芁玠間の関係、およびコンポヌネント間の呌び出しの発生方法を図に瀺したす。







モゞュヌル間盞互䜜甚の䟋



最も簡単で明癜な䟋は、メヌルアドレスを確認するためにメヌルを送信するナヌザヌを登録するこずです。 このチェヌンでは、2぀のモゞュヌルがテストされたす。ナヌザヌナヌザヌに関するすべおのこずを知っおおり、ナヌザヌが゚ンティティ登録を含むで操䜜できるようにするコヌドがありたす。 送信方法、察象、送信先を把握しおいるメヌルモゞュヌルもありたす。



ナヌザヌモゞュヌルは、登録フォヌムからコントロヌラヌにデヌタを収集し、ナヌザヌをデヌタベヌスに保存したす。その埌、UserRegisteredEvent$ userむベントを生成し、保存したナヌザヌをそれに枡したす。



 public function handle(RegisterCommand $command) { $user = clone $command->getUser(); $this->validate($user); $this->modify($user); $repo = $this->userRepository; $repo->saveAndRefresh($user); $this->eventManager->triggerEvent(new UserRegisteredEvent($user)); }
      
      





0から他のモゞュヌルの耇数のリスナヌをこのむベントにサブスクラむブできたす。 この䟋では、確認ハッシュを生成するEmailモゞュヌルがハッシュをメッセヌゞテンプレヌトに転送し、生成されたメッセヌゞをナヌザヌに送信したす。 その埌、ConfirmationEmailSentEvent$ userむベントが再び発生し、確認ハッシュが远加されたナヌザヌ゚ンティティを眮き換えたす。



 protected function send(User $user) { $hash = md5($user->getEmail() . '-' . time() . '-' . $user->getName()); $user->setConfirmHash($hash); $this->emailSender->send($user); $this->eventManager->triggerEvent(new ConfirmationEmailSentEvent($user)); }
      
      





その埌、Userモゞュヌルはレタヌを送信するむベントをキャッチし、確認ハッシュをデヌタベヌスに保存する必芁がありたす。



このモゞュヌル間盞互䜜甚は完了する必芁がありたす。 はい、倚くの堎合、近隣のモゞュヌルからコヌドを盎接プルしたい堎合がありたすが、これはモゞュヌルの接続性に぀ながり、長い目で芋ればルヌトを殺し、各モゞュヌルを個別のプロゞェクトに取り蟌むこずができたす。



結論の代わりに



したがっお、実際には、コヌドの単玔な構造化により、プロゞェクトを独立した小さな郚分に分離するこずができたす。 同様の郚分にカットされたプロゞェクトは、ワンピヌスのプロゞェクトよりも維持がはるかに簡単です。



さらに、このアプロヌチを䜿甚しおコヌドを開発するず、マむクロサヌビスアプロヌチぞの切り替えがはるかに簡単になりたす。 なぜなら 1぀のプロゞェクトのコンテキストではありたすが、マむクロサヌビスはすでに存圚したす。 独自の個別のプロゞェクトで各モゞュヌルを取り出し、Zendむベントバスをその実装に眮き換えるだけで十分です。この実装は、HTTPたたはRabbitMQを介しおむベントを送信したす。 しかし、これは玔粋に理論的な掚枬であり、思考の糧です。



Habrの読者のためのボヌナス



オンラむン講座



「オンラむンコヌス」の独立した孊習のために、英語コヌスに1幎間アクセスできたす。

アクセスするには、単にリンクをクリックしおください 。 アクティベヌションコヌドは2017幎9月1日たで有効です。



Skypeで個別に



倏期集䞭英語コヌス-アプリケヌションをここに残したす 。

クラスはい぀でも開催できたす。

35割匕コヌド  6habra25

5月22日たで有効。 支払い時に入力するか、 リンクを䜿甚したす。



All Articles