Por trás do site IngressoPrático - Os serviços estão em toda parte - parte 4
Eriksen Costa12.12.2011 - 15:00(upd: 25.07.2013 - 19:03)
Você está lendo a parte quatro de Por trás do site IngressoPrático, uma série em quatro posts que mostra como nós usamos o Symfony2 para desenvolver o site.
O Container de Serviço do Symfony2 é de grande ajuda para criar código desacoplado e reusável. O framework em si o usa extensivamente, é por isso que é tão flexível. O resultado disso é que é fácil criar uma arquitetura orientada a serviços. Os serviços estão em toda parte do IngressoPrático, eu separei mais dois serviços: um que é muito simples e outro que é um pouco mais complexo.
Os trechos de código e diagramas de classe foram simplificados com o objetivo de ilustrar como nós organizamos o código. Eu adicionei referências onde você encontrará informações mais detalhadas sobre um determinado assunto.
Formatação de moeda
Como o IngressoPrático é um site de e-commerce, existem valores monetários exibidos em toda parte:
Valores monetários numa página de pedido
Valores monetários na interface de administração (clique para aumentar)
Os ingressos são vendidos apenas em reais (BRL). Durante o desenvolvimento, nós apenas exibimos o valor para um pedido ou preço de ingresso sem nenhuma formatação adicional.
Quando a aplicação estava próxima do lançamento, nós precisávamos fazer algo em relação a formatação desses valores. É tentador apenas concatenar o símbolo da moeda no valor mas o separador decimal para o Real não é o ponto (”.”) e sim a vírgula (”,”). Felizmente, o Symfony2 tem um componente Locale que abraça o padrão ICU através das classes do php-intl. Era apenas uma questão de criar uma extensão Twig com um filtro Twig que aceita um símbolo de moeda e opcionalmente o locale desejado.
Como você já deve saber, é fácil registrar uma extensão Twig.
Finalmente, os templates que exibem valores monetários tem que apenas usar o filtro Twig currency.
Simples, não?
Cart
Uma implementação de cart é por natureza um serviço. Nossa implementação inicial do Cart foi feita como uma forma de praticar e discutir TDD. Foi feita em uma hora (a única hora de pair programming do projeto :) e foi a implementação de cart mais simples (e útil) que tinha visto na vida.
O seguinte diagrama de classes mostra como estruturamos o cart:
A arquitetura é realmente simples. Nós temos um serviço Cart (IngressoPratico\CartBundle\Service\Cart
). Este serviço expõe uma API simples com as operações que precisamos.
De forma simples, um serviço Cart cria uma entidade Cart e persiste-a com o método Cart::save(). Note que este método invoca a validação e é neste momento que é verificado a disponibilidade de um item. Nós criamos um Validation Constraint customizado chamado ItemAvailable que nós colocamos na classe IngressoPratico\CartBundle\Entity\Item
.
Existe uma página no cookbook da documentação do Symfony que explica como criar um Validation Constrain customizado. Nossa classe Constraint ItemAvalable apenas verifica se já existe um item em outro cart, em um pedido já pago ou se o item está disponível no catálogo (publicado ou não).
E um cart não é um cart sem um checkout. Quando o método Cart::checkout() é invocado, apenas dispara um evento CartCheckoutEvent. Mas como um Order (pedido) é criado? É simples, existe um OrderSubscriber que aguarda por um evento CartCheckoutEvent. É este subscriber que cria o Order (pedido). Depois de dispachar o evento, Cart::checkout() apaga a instância da entidade Cart.
Nosso OrderSubscriber (IngressoPratico\OrderBundle\Event\OrderSubscriber
) apenas usa o serviço Order (IngressoPratico\OrderBundle\Service\Order
) que cria uma entidade Order (IngressoPratico\OrderBundle\Entity\Order
) usando o Item do CatalogBundle (IngressoPratico\CatalogBundle\Entity\Item
) que é empacotado pela classe Item do CartBundle (IngressoPratico\CartBundle\Entity\Item
).
E apenas para completar, a configuração dos serviços. O serviço Cart agora é fácil de ser usar de qualquer parte da aplicação, seja em um controller ou em um outro serviço.
É isto! Existem muitos outros serviços no IngressoPrático como o serviço Quota (cota), o menu do admin (baseado no KnpMenuBundle) e alguns outros. Quando você se acostuma com o design pattern Dependency Injection e com o Container de Serviço, você irá perceber como é fácil desacoplar os seus objetos, com enormes benefícios para o teste de unidade. Usar o EventDispatcher também é uma boa forma de não inchar uma classe com preocupação que não pertence a ela (como no caso do serviço Cart e a criação de um Order no checkout).