Revath Kumar tem um bom artigo sobre a extração lógica de domínio a partir de controllers e como colocar essa lógica em uma classe de serviço. Depois de lê-lo, eu comentei que, com um pouco de trabalho extra, seria fácil modificar o exemplo para algo mais perto do padrão Action-Domain-Responder. Ao fazer isso, obteríamos uma melhor separação de interesses (especialmente na apresentação).
Usando o código que Revath dá em seu texto como base, podemos fazer o seguinte:
- Na classe de serviço, em vez de, por vezes, gerar exceções e às vezes retornar arrays, nós sempre vamos retornar instâncias Payload. Isso menciona explicitamente o resultado da atividade de domínio (“input não é válido”, “ordem criada”, “ordem não criada”, “erro”). Tornar as informações de status explícitas no Payload significa que não precisamos pegar as exceções na ação do controller, e que nós não precisamos examinar o objetos de domínio para interpretar o que ocorreu no domínio. A classe de serviço pode agora dizer explicitamente o que ocorreu de uma forma padronizada.
- Na ação do controller, agora que não precisamos pegar as exceções, podemos nos concentrar em um conjunto muito menor de lógica: obter a entrada do usuário, passá-la para o domínio, voltar o payload do domínio, e passar o payload a um responder.
- Finalmente, apresentamos um Responder, cujo trabalho é construir (e, nesse caso, enviar) a resposta. A lógica do responder acaba sendo simplificada também.
O código modificado se parece com isto:
use AuraPayloadPayload; class OrdersController extends Controller { public function actionCreate() { $orderData = Yii::app()->request->getParam('order'); $order = new OrdersService(); $payload = $order->create($orderData); $responder = new OrdersResponder(); $responder->sendCreateResponse($payload); } } class OrdersResponder extends Responder { public function sendCreateResponse($payload) { $result = array('messages' => $payload->getMessages()); if ($payload->getStatus() === Payload::CREATED) { $this->_sendResponse(200, $result); } else { $result['status'] = 'error'; $this->_sendResponse(403, $result); } } } class OrdersService { protected function newPayload($status) { return new Payload($status); } public function create($orderData) { if(empty($orderData['items'])) { return $this->newPayload(Payload::NOT_VALID) ->setMessages([ "Order items can't be empty." ]); } $items = $orderData['items']; unset($orderData['items']); try { $order = new Orders; $orderTransaction = $order->dbConnection->beginTransaction(); $address = Addresses::createIfDidntExist($orderData); unset($orderData['address']); $orderData['address_id'] = $address->id; $amount = 0; foreach ($items as $key => $item) { $amount += $item['total']; } $amount += $orderData['extra_charge']; $orderData['amount'] = $amount; $order->attributes = $orderData; if($order->save()) { if(OrderItems::batchSave($items, $order->id)) { $orderTransaction->commit(); $this->sendMail($order->id); return $this->newPayload(Payload::CREATED) ->setMessages([ "Order placed successfully." ]); } $orderTransaction->rollback(); return $this->newPayload(Payload::NOT_CREATED) ->setMessages([ "Failed to save the items." ]); } else { // handle validation errors $orderTransaction->rollback(); return $this->newPayload(Payload::ERROR) ->setMessages($order->getErrors()); } } catch(Exception $e) { $orderTransaction->rollback(); return $this->newPayload(Payload::ERROR) ->setMessages([ "Something wrong happened" ]); } } }
Ainda há candidatos óbvios para melhoria aqui. Por exemplo, poderíamos começar a separar os métodos de ação do controller em suas próprias classes de ação individuais. Mas pequenos passos são o caminho certo para a refatoração.
Esse pequeno conjunto de alterações nos dá uma melhor separação de interesses, especialmente em termos de apresentação. Lembre-se, a “apresentação” em um ambiente de request/resposta é toda a resposta HTTP, e não apenas o corpo da resposta. As alterações acima fazem com que os cabeçalhos HTTP e o trabalho da apresentação do status do código não sejam misturados com o controller; eles agora são manipulados por um objeto Responder separado.
***
Paul M. Jones faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: http://paul-m-jones.com/archives/6172
Mensagem do anunciante:
Conheça a Umbler, startup de Cloud Hosting por demanda feita para agências e desenvolvedores. Experimente grátis!
Powered by WPeMatico