Skip to content

Instantly share code, notes, and snippets.

@patrickheeney
Last active August 29, 2015 14:06
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save patrickheeney/c4a192cff86194c859f5 to your computer and use it in GitHub Desktop.
Save patrickheeney/c4a192cff86194c859f5 to your computer and use it in GitHub Desktop.
Application Lifecycle

September 25, 2014

Request Cycle (with Service Layer)

  • HTTP Request -> Received by Controller.
  • Controller -> Serializes request data into command.
  • Controller -> Passes Command off to Command Bus.
  • CommandBus -> Resolves Command to Handler.
  • Command Handler -> Receives request and resolves any dependencies, fires any command events to modify the command data before and after handler action, then passes it off to a service layer.
  • Service -> Receives input and handles creation, retreiving and updating entities. It builds up the entities and related entities and hands it off to the repository for persisting and deleting.
  • Repository (Active Record) -> Receives updated entity and converts entity to a model. Saves AR Model.
  • Repostiory (Data Mapper) -> Receives updated entity and converts to a data mapper entity. Calls data mapper persist.
  • Repository (NoSQL) -> Receives updated entity and converts to nosql insert/update/delete queries and persists changes.
  • Service -> Receives the repository response and convert the respose to a new entity
  • Service -> Dispatch aggregated events on entities and return entities
  • Command Handler -> return Entities in a Response
  • Controller -> receives command response and process it, sets view data, handles redirects, set headers, etc
  • Response -> built by the controller and now dispatched

Request Cycle (without Service Layer)

  • HTTP Request -> Received by Controller.
  • Controller -> Serializes request data into command.
  • Controller -> Passes Command off to Command Bus.
  • CommandBus -> Resolves Command to Handler.
  • Command Handler -> Receives request and resolves any dependencies, fires any command events to modify the command data before and after handler action. It handles creation, retrieving, updating, and builds up the entities and related entities and passes it off to the repository for persisting and deleting.
  • Repository (Active Record) -> Receives updated entity and converts entity to a model and related models. Saves AR Model.
  • Repostiory (Data Mapper) -> Receives updated entity and converts to a data mapper entity. Calls data mapper persist.
  • Repository (NoSQL) -> Receives updated entity and converts to nosql insert/update/delete queries and persists changes.
  • Command Handler -> Receives the updated entity from the repository and dispatch aggregated events from the repository and return entities.
  • Command Handler -> Return Entities in a Response.
  • Controller -> Receives command response and process it, sets view data, handles redirects, set headers, etc
  • Response -> Built by the controller and now dispatched.

Example Request

Logic In Command Handler

  • HTTP Request - GET /orders/create
  • Controller - OrdersController - create method
  • Controller - OrdersController
$this->exec(new CreateOrder($request->all()));
  • CommandBus - CreateOrder -> CreateOrderHandler
  • Command Handler
$entity = $this->orderRepository->new($command->data('order'));
$order = $this->orderRepository->save($entity);

foreach($command->data('items') as $item) {
  $entity = $this->itemRepository->new(array_merge(['order_id' => $order->getId()], $item));
  $item = $this->itemRepository->save($entity);
  $order->addItem($item);
}

return ['order' => $order];
  • Controller
return View::make('list', $handlerData);

Pros / Cons

  • All logic is in the handler, so its easier to know where it exists.
  • Handler is now in charge of multiple repositories and communication between them rather than forwarding calls to the repository or a service.
  • Would need to raise domain events within the handler and also dispatch them.

Logic In Model

  • Storing the logic in the model would not work, because if you change the repository implementation to something that does not have active records (data mapper, nosql, etc) then your logic would need to be rebuilt elsewhere.

Logic In Repository

  • HTTP Request - GET /orders/create
  • Controller - OrdersController - create method
  • Controller - OrdersController
$this->exec(new CreateOrder($request->all()));
  • CommandBus - CreateOrder -> CreateOrderHandler
  • Command Handler
$order = $this->orderRepository->create($command->data());

return ['order' => $order];
  • Repository
$model = $this->model->newInstance();
$model->fill($data);
$model->save();

$itemClass = $this->getClassName($this->model->items());

$items = [];
foreach($data['items'] as $itemData) {
  $item = new $itemClass;
  $item->fill(array_merge(['order_id' => $model->getId()], $itemData));
  $item->save();
  
  $items[] = $item;
}

$model->attach($items);

return $model;
  • Controller
return View::make('list', $handlerData);

Pros / Cons

  • Repository is reponsible for any creation, updating, deleting and handling any input data in one central place.
  • Repository is now reponsible for more than persistance and has to create the objects before persisting them.
  • Repository has to be aware of any relations to populate them based on input data.
  • Repository would need to raise domain events for the handler to dispatch. This means multiple repositories would have multiple sets of events that would need collected and dispatched from the handler.

Logic In Service Layer

  • HTTP Request - GET /orders/create
  • Controller - OrdersController - create method
  • Controller - OrdersController
$this->exec(new CreateOrder($request->all()));
  • CommandBus - CreateOrder -> CreateOrderHandler
  • Command Handler
$order = $this->orderCreatorService->create($command->data());

return ['order' => $order];
  • Service
$model = $this->orderRepository->newInstance();
$model->fill($data);
$model->save();

$items = [];
foreach($data['items'] as $itemData) {
  $item = $this->itemRepository->newInstance();
  $item->fill(array_merge(['order_id' => $model->getId()], $itemData));
  $item->save();
  
  $items[] = $item;
}

$model->attach($items);

return $model;
  • Controller
return View::make('list', $handlerData);

Pros / Cons

  • Service layer sits in front of any repository actions. If you need to implement caching then its as simple as tossing it in the method before forwarding the call to the repository. You wouldn't want your repository to be aware that it is being cached.
  • Separates concerns as the repository is not building maintaining complex relationships, it is only reponsible for persisting or finding data related to its Entity/Model.
  • The service layer could convert any ActiveRecord models to DumbAbstractEntities to create a central point to access any repositories. If the repository converted it, then the repository becomes the central place to access any models/enities and would then need to manage creating, updating, etc.
  • The service layer would be the central point for any domain events. They could easily be collected from the service to be handled layer.
  • Adds another layer.
  • Commands just forward calls onto services, why not get rid of the command and just call the service then?
  • The service layer could grow quite large and need broken down into separte services (OrderCreationService, OrderPaymentService, etc).

Layers

Commands / Handler Layer

The point of this is to abstract the business logic out of the controller. Each controller action has its own set of unique dependencies and by extract them to commands they are isoloted and perform only the actions necessary for that command. ViewPostListing, CreateNewPost, etc.

Service Layer

The point of this layer is to handle interacting with the different repositories. An order for example is complex and has customers, order items, etc. We cant put this logic in a single repository so it has to exist another layer up as the repositories are responsible for interacting further down the stack. It also provides a layer to implement caching on find() operations. If this layer becomes too complex it may need to be broken out into EntityReaderService, EntityCreatorService, etc.

Service Layer (Arguments Against)

On the other hand, with a service layer we would need to be aware of the inner workings of the repository. Take for example and Order Entity that has Order Items within it. The Order Items exist only as a relation to the Order Entity. If the service layer is building the separate entities and communicating with a OrderRepository and a OrderItemRepository then it knows to much about how the data is persisted. In a NoSQL repository, all the data and relations would be stored within the same backend "table" or "collection" so it would not need to interact with any anything else. It would not have a separate repository for order items. The order items, customer data, etc is all part of that one repository / document.

On the other hand an SQL repository is going to need to update and maintain relations for that entity. So maybe a service layer is only necessary when you need to communicate with two separate repositories that are not related to each other, like updating an Order Address and a Customer Address. Then you would need a CustomerAddressUpdatorService that would interact with the different repositories across the unrelated entities.

Repository Layer

The point of this layer is to abstract away from interacting with the model directly. This way the backend can be completely changed and we would only need to create a new Repository that implements the same interface that interacts with the new ORM, data mapper, nosql, etc. A NoSqlRepository for exmaple is going to save all related columns and entities within the same document. An SQLRepository is going to updated the related models.

Duplicate Entities

We need to create a blank class used to store the entities as a Data Transfer Object (DTO). This DTO will have ArrayCollection for relationships and will have the current state of the entity to pass around. The command handler can communicate with the service layer to build this entity and return it, pass this entity through events for modification. Upon receiving the final entity, it should be validated at some point, and each entity relationship with data will be sent to the correct repository to be saved. This entity will also be what the service layer responds with so the views can interact with an entity that can't be changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment