SlideShare a Scribd company logo
1 of 132
Manel López / @mloptor #DrupalcampSpain2015
Desacoplar la lógica de negocio del
framework
Manel López / @mloptor #DrupalcampSpain2015
Manel López Torrent
Ingeniero en informática
Web Developer
@mloptor
malotor@gmail.com
Manel López / @mloptor #DrupalcampSpain2015
Desacoplada
=
Independiente
Manel López / @mloptor #DrupalcampSpain2015
“El dominio es el mundo del
negocio”
Ideas, conocimiento y datos del
problema que intentamos resolver.
Expertos del dominio
Dominio
Manel López / @mloptor #DrupalcampSpain2015
Objeto del negocio
Objetos o entidades abstractas
que conforman el dominio.
Manel López / @mloptor #DrupalcampSpain2015
UniversidadPeriódico
Profesor
Alumno Asignatura
Departament
o
Editor
Periodista
Noticia
Anuncio
Manel López / @mloptor #DrupalcampSpain2015
Reglas de negocio
Manel López / @mloptor #DrupalcampSpain2015
Modelo del dominio
Nuestra solución al problema
Conocimiento estructurado del
problema
Código, prosa o un diagrama.
Refleja el conocimiento que tenemos
del dominio
Manel López / @mloptor #DrupalcampSpain2015
Lógica de negocio / Capa de negocio
“La parte de nuestro software que
mantiene el modelo del dominio y
codifica las reglas de negocio”
Manel López / @mloptor #DrupalcampSpain2015
Aplicaciones web y Frameworks
Manel López / @mloptor #DrupalcampSpain2015
¿Dónde se
implementa la
lógica de
negocio?
Manel López / @mloptor #DrupalcampSpain2015
ControladorPetición
Modelo
Vista
Persistencia
de datos
Respuesta
Presentación
de los datos
Manel López / @mloptor #DrupalcampSpain2015
ControladorPetición
Modelo
Vista
Persistencia
de datos
Respuesta
Lógica de
negocio
Presentación
de los datos
Manel López / @mloptor #DrupalcampSpain2015
Problemas
Alto nivel de dependencia
Difícil testeo
No SRP
HTTP
Tamaño
Manel López / @mloptor #DrupalcampSpain2015
Hi! I'm your
controller
Manel López / @mloptor #DrupalcampSpain2015
Controlador
Lógica de
negocio
?
Manel López / @mloptor #DrupalcampSpain2015
Arquitectura hexagonal
Manel López / @mloptor #DrupalcampSpain2015
Arquitectura hexagonal
Arquitectura de software
Alistair CockBurn
AKA “Ports and adapters”
Separar responsabilidades
Manel López / @mloptor #DrupalcampSpain2015
Domain
Model
Lógica
de
negocio
Manel López / @mloptor #DrupalcampSpain2015
Application Layer
Domain
Model
API
Manel López / @mloptor #DrupalcampSpain2015
Application Layer
Domain
Model
Puerto
Puerto
Puerto
Puerto
Manel López / @mloptor #DrupalcampSpain2015
Application Layer
Domain
Model
Infrastructure Layer Adaptador
Manel López / @mloptor #DrupalcampSpain2015
Lógica de
negocio
Framework
Manel López / @mloptor #DrupalcampSpain2015
Puertos/Adaptadores
Manel López / @mloptor #DrupalcampSpain2015
interface log {
public function message($message);
public function error($message);
}
class LogService implements log {
public function __construct($factory) {
$this->loggerFactory = $factory;
}
public function message($message) {
$this->loggerFactory->get('my_module')->error($message);
}
public function error($message) {
$this->loggerFactory->get('my_module')->error($message);
}
}
Adaptador
Puerto
Manel López / @mloptor #DrupalcampSpain2015
Application Layer
Domain
Model
Manel López / @mloptor #DrupalcampSpain2015
Application Layer
Domain
Model
Manel López / @mloptor #DrupalcampSpain2015
Application Layer
Domain
Model
Manel López / @mloptor #DrupalcampSpain2015
Application Layer
Domain
Model
Manel López / @mloptor #DrupalcampSpain2015
Fronteras
Manel López / @mloptor #DrupalcampSpain2015
Application Layer
Domain
Model
Infrastructure Layer
Manel López / @mloptor #DrupalcampSpain2015
Ventajas
Manel López / @mloptor #DrupalcampSpain2015
Agnóstica
Manel López / @mloptor #DrupalcampSpain2015
Fácil Testeo
Manel López / @mloptor #DrupalcampSpain2015
Aislada
Cambios aislados
+ refactoring
_ deuda técnica
Manel López / @mloptor #DrupalcampSpain2015
Patrones de diseño
Manel López / @mloptor #DrupalcampSpain2015
Entidad
Representan a los objetos del dominio
Estado + Comportamiento
Contener lógica del negocio
Modelo Anémico
Identidad
Manel López / @mloptor #DrupalcampSpain2015
Adaptador
Adaptadores
Puertos
Manel López / @mloptor #DrupalcampSpain2015
Factorías
stdClass new Product()
Manel López / @mloptor #DrupalcampSpain2015
Repositorios
RepositorioCliente
DataProvider
Factoría
1 - Query
2 - Query 3 - Object Data
4 - Object Data
5 - Real Object6 - Real Object
Manel López / @mloptor #DrupalcampSpain2015
Fachada
Manel López / @mloptor #DrupalcampSpain2015
Esto no es el
Santo Grial
Manel López / @mloptor #DrupalcampSpain2015
Escalabilidad
Mantenibilidad
fiabilidad
Complejidad
Manel López / @mloptor #DrupalcampSpain2015
CRUD
Mínima lógica
Complejidad Oculta
Escenarios Inciertos
Requisitos cambiantes
Desconocimiento del Dominio
Manel López / @mloptor #DrupalcampSpain2015
SHOW ME
THE
CODE!!!
Manel López / @mloptor #DrupalcampSpain2015
Manel López / @mloptor #DrupalcampSpain2015
Shopping Cart
1. CLIENTE Quiero ver un listado de productos para elegir lo que quiero
comprar
2. CLIENTE Quiero ser capaz de elegir el producto que quiero comprar y
añadirlo a mi cesta de la compra
a. Si un producto está ya en la cesta incrementaremos la cantidad
3. CLIENTE Quiero ser capaz de eliminar productos de mi cesta si he
cambiado de opinión
4. CLIENTE Quero poder ver el coste total de todos los productos de mi
cesta en todo momento.
5. CLIENTE Quiero que se guarde mi cesta de la compra por si quiero
pensarlo mejor
Manel López / @mloptor #DrupalcampSpain2015
Repositorios de código
Shopping Cart Business Logic
https://github.com/malotor/basket
Shopping Cart Drupal 8 module
https://github.com/malotor/drupal_ecommerce
Manel López / @mloptor #DrupalcampSpain2015
Modelo del dominio
Diseño emergente
Manel López / @mloptor #DrupalcampSpain2015
Tests unitarios Tests integración
Manel López / @mloptor #DrupalcampSpain2015
Modelo del dominio
Manel López / @mloptor #DrupalcampSpain2015
Basket Products
contains
*
Manel López / @mloptor #DrupalcampSpain2015
src
├── Application
│ ├── Factory
│ │ ├── BasketFactory.php
│ │ └── ProductFactory.php
│ ├── Repository
│ │ ├── BasketRepository.php
│ │ └── ProductRepository.php
│ └── Service
│ └── ShoppingCartService.php
└── Domain
└── Entity
├── Basket.php
├── Item.php
└── Product.php
Capa Aplicación
Capa Dominio
Manel López / @mloptor #DrupalcampSpain2015
Capa Domino - Entidades
Manel López / @mloptor #DrupalcampSpain2015
/src/Domain/Entity/Product.php
class Product implements Item
{
private $id;
private $quantity;
private $price;
public function __construct($id, $price, $quantity = 1) {}
public function getId() {}
public function getQuantity() {}
public function getAmount() {}
public function increaseQuantity() {}
}
Manel López / @mloptor #DrupalcampSpain2015
/src/Domain/Entity/Product.php
class Product implements Item
{
private $id;
private $quantity;
private $price;
public function __construct($id, $price, $quantity = 1) {}
public function getId() {}
public function getQuantity() {}
public function getAmount() {}
public function increaseQuantity() {}
}
Estado
Comportamiento
Manel López / @mloptor #DrupalcampSpain2015
/src/Domain/Entity/Basket.php
class Basket
{
private $items = [];
public function countItems() {}
public function addItem(Item $item) {}
public function getItem($itemId) {}
public function removeItem($itemId){}
public function getItems(){}
public function totalAmount(){}
}
Manel López / @mloptor #DrupalcampSpain2015
/src/Domain/Entity/Basket.php
class Basket
{
public function addItem(Item $item)
{
$itemId = $item->getId();
if ($this->containsItem($itemId))
$this->items[$itemId]->increaseQuantity();
else
$this->items[$itemId] = $item;
}
}
“Si un producto ya existe en el carro
incrementaremos su cantidad”
Manel López / @mloptor #DrupalcampSpain2015
Capa Dominio - Test de integración
Manel López / @mloptor #DrupalcampSpain2015
/tests/Integration/Basket.php
class BasketTest extends PHPUnit_Framework_TestCase {
protected $basket;
public function setUp() {
$this->products[0] = new Product(1,10);
$this->products[1] = new Product(2,5.5);
$this->basket = new Basket();
}
public function testAddAProduct() {
$this->assertEquals(0, $this->basket->countItems());
$this->basket->addItem($this->products[0]);
$this->assertEquals(1, $this->basket->countItems());
}
}
Manel López / @mloptor #DrupalcampSpain2015
/tests/Integration/Basket.php
class BasketTest extends PHPUnit_Framework_TestCase {
public function testRemoveAProduct() {
$this->basket->addItem($this->products[0]);
$this->basket->addItem($this->products[1]);
$this->basket->removeItem(1);
$this->assertEquals(1, $this->basket->countItems());
}
public function testIncreaseProductQuantity() {
$this->assertEquals(1, $this->products[0]->getQuantity());
$this->basket->addItem($this->products[0]);
$this->basket->addItem($this->products[0]);
$this->assertEquals(1, $this->basket->countItems());
$this->assertEquals(2, $this->products[0]->getQuantity());
}
}
Manel López / @mloptor #DrupalcampSpain2015
/tests/Integration/Basket.php
class BasketTest extends PHPUnit_Framework_TestCase {
public function testCartTotalAmount() {
$this->basket->addItem($this->products[0]);
$this->basket->addItem($this->products[0]);
$this->basket->addItem($this->products[1]);
$this->assertEquals(25.5, $this->basket->totalAmount());
}
}
Manel López / @mloptor #DrupalcampSpain2015
Capa Aplicación - Servicios
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Service/ShoppingCartService.php
use malotorshoppingcartApplicationRepositoryProductRepository;
use malotorshoppingcartApplicationRepositoryBasketRepository;
class ShoppingCartService
{
private $productRepository;
private $basketRepository;
public function __construct(
ProductRepository $productRepository,
BasketRepository $basketRepository
)
{...}
}
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Service/ShoppingCartService.php
use malotorshoppingcartApplicationRepositoryProductRepository;
use malotorshoppingcartApplicationRepositoryBasketRepository;
class ShoppingCartService
{
private $productRepository;
private $basketRepository;
public function __construct(
ProductRepository $productRepository,
BasketRepository $basketRepository
)
{...}
}
Puertos
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Service/ShoppingCartService.php
use malotorshoppingcartApplicationRepositoryProductRepository;
use malotorshoppingcartApplicationRepositoryBasketRepository;
class ShoppingCartService
{
private $productRepository;
private $basketRepository;
public function __construct(
ProductRepository $productRepository,
BasketRepository $basketRepository
)
{...}
}
Interfaces
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Service/ShoppingCartService.php
class ShoppingCartService
{
{...}
public function addProductToBasket($productId, $baskedId)
{...}
public function removeProductFromBasket($productId, $basketId)
{...}
public function getProductsFromBasket($basketId) {...}
public function getBasketTotalAmount($basketId) {...}
}
API para Infraestructura
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Service/ShoppingCartService.php
class ShoppingCartService
{
public function addProductToBasket($productId, $baskedId)
{
$basket = $this->basketRepository->get($baskedId);
$product = $this->productRepository->get($productId);
$basket->addItem($product);
$this->basketRepository->save($basket);
}
{...}
}
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Service/ShoppingCartService.php
class ShoppingCartService
{
public function addProductToBasket($productId, $baskedId)
{
$basket = $this->basketRepository->get($baskedId);
$product = $this->productRepository->get($productId);
$basket->addItem($product);
$this->basketRepository->save($basket);
}
{...}
}
Recupera datos al los repositorios
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Service/ShoppingCartService.php
class ShoppingCartService
{
public function addProductToBasket($productId, $baskedId)
{
$basket = $this->basketRepository->get($baskedId);
$product = $this->productRepository->get($productId);
$basket->addItem($product);
$this->basketRepository->save($basket);
}
{...}
}
Ejecutará la lógica de negocio
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Service/ShoppingCartService.php
class ShoppingCartService
{
public function addProductToBasket($productId, $baskedId)
{
$basket = $this->basketRepository->get($baskedId);
$product = $this->productRepository->get($productId);
$basket->addItem($product);
$this->basketRepository->save($basket);
}
{...}
}
Persiste los datos
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Service/ShoppingCartService.php
class ShoppingCartService
{
public function addProductToBasket($productId, $baskedId)
{
$basket = $this->basketRepository->get($baskedId);
$product = $this->productRepository->get($productId);
$basket->addItem($product);
$this->basketRepository->save($basket);
}
{...}
}
Patrón Fachada
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Service/ShoppingCartService.php
class ShoppingCartService
{
public function removeProductFromBasket($productId, $basketId)
{
$basket = $this->basketRepository->get($basketId);
$basket->removeItem($productId);
$this->basketRepository->save($basket);
}
public function getProductsFromBasket($basketId)
{
$basket = $this->basketRepository->get($basketId);
return $basket->getItems();
}
public function getBasketTotalAmount($basketId)
{
$basket = $this->basketRepository->get($basketId);
return $basket->totalAmount();
}
}
Manel López / @mloptor #DrupalcampSpain2015
Capa Aplicación - Repositorios (Puertos)
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Repository/BasketRepository.php
namespace malotorshoppingcartApplicationRepository;
use malotorshoppingcartDomainEntityBasket;
interface BasketRepository
{
/**
* @return Basket
*/
public function get($basketId);
public function save(Basket $basket);
}
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Repository/BasketRepository.php
namespace malotorshoppingcartApplicationRepository;
use malotorshoppingcartDomainEntityBasket;
interface BasketRepository
{
/**
* @return Basket
*/
public function get($basketId);
public function save(Basket $basket);
}
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Repository/ProductRepository.php
namespace malotorshoppingcartApplicationRepository;
use malotorshoppingcartDomainEntityProduct;
interface ProductRepository
{
/**
* @return Product
*/
public function get($productId);
}
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Repository/ProductRepository.php
namespace malotorshoppingcartApplicationRepository;
use malotorshoppingcartDomainEntityProduct;
interface ProductRepository
{
/**
* @return Product
*/
public function get($productId);
}
Manel López / @mloptor #DrupalcampSpain2015
Capa Aplicación - Factorías
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Factory/Product.php
use malotorshoppingcartDomainEntityProduct;
class ProductFactory
{
static public function create($object) {
return new Product(
$object->id,
$object->price,
$object->quantity
);
}
}
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Factory/Product.php
use malotorshoppingcartDomainEntityProduct;
class ProductFactory
{
static public function create($object) {
return new Product(
$object->id,
$object->price,
$object->quantity
);
}
}
stdClass new Product()
Manel López / @mloptor #DrupalcampSpain2015
/src/Application/Factory/Basket.php
use malotorshoppingcartDomainEntityBasket;
class BasketFactory
{
static public function create($products = array()) {
$basket = new Basket();
foreach ($products as $object) {
$basket->addItem(
ProductFactory::create($object) );
}
return $basket;
}
}
Manel López / @mloptor #DrupalcampSpain2015
Empaquetar y distribuir
Manel López / @mloptor #DrupalcampSpain2015
/composer.json
{
"name": "malotor/shoppingcart",
"description": "Shopping Cart",
"version" : "2.0.0-dev",
"require-dev": {
"phpunit/phpunit": "~4.4",
"phpspec/phpspec": "~2.0"
},
"authors": [
{
"name": "Manel López",
"email": "malotor@gmail.com"
}
],
"require": {
"php": ">=5.4.0"
},
"autoload": {
"psr-4": {
"malotorshoppingcart": "src"
}
}
}
https://packagist.org/
Manel López / @mloptor #DrupalcampSpain2015
Infraestructura
Manel López / @mloptor #DrupalcampSpain2015
Manel López / @mloptor #DrupalcampSpain2015
Módulo Drupal 8
$ drupal generate:module
Manel López / @mloptor #DrupalcampSpain2015
Cargar la lógica
Manel López / @mloptor #DrupalcampSpain2015
/modules/ecommerce/composer.json
{
"name": "malotor/drupal8_shoppingcart",
"description": "Example module",
"type": "drupal-module",
"repositories": [
{
"type": "vcs",
"url": "https://github.com/malotor/basket.git"
}
],
"require": {
"malotor/shoppingcart": "dev-master"
}
}
Manel López / @mloptor #DrupalcampSpain2015
Cargamos las dependencias ( composer_manager)
$ drush dl composer_manager
$ drush en -y composer_manager
$ drush composer-manager-init
$ cd core
$ composer drupal-update
Manel López / @mloptor #DrupalcampSpain2015
Rutas
Manel López / @mloptor #DrupalcampSpain2015
/ecommerce.routing.yml
ecommerce.addtocart:
path: '/ecommerce/addtocart/{productId}'
defaults:
_controller: 'DrupalecommerceControllerEcommerceController::addToCart'
_title: 'Add to cart'
requirements:
_permission: 'access content'
ecommerce.removefromcart:
path: '/ecommerce/removefromcart/{productId}'
defaults:
_controller: 'DrupalecommerceControllerEcommerceController::removeFromCart'
_title: 'Remove from cart'
requirements:
_permission: 'access content'
ecommerce.showcart:
path: '/ecommerce/shoppingcart'
defaults:
_controller: 'DrupalecommerceControllerEcommerceController::showCart'
_title: 'Shopping Cart'
requirements:
_permission: 'access content'
Manel López / @mloptor #DrupalcampSpain2015
/ecommerce.routing.yml
ecommerce.addtocart:
path: '/ecommerce/addtocart/{productId}'
defaults:
_controller: 'DrupalecommerceControllerEcommerceController::addToCart'
_title: 'Add to cart'
requirements:
_permission: 'access content'
ecommerce.removefromcart:
path: '/ecommerce/removefromcart/{productId}'
defaults:
_controller: 'DrupalecommerceControllerEcommerceController::removeFromCart'
_title: 'Remove from cart'
requirements:
_permission: 'access content'
ecommerce.showcart:
path: '/ecommerce/shoppingcart'
defaults:
_controller: 'DrupalecommerceControllerEcommerceController::showCart'
_title: 'Shopping Cart'
requirements:
_permission: 'access content'
Añadir al carro
Eliminar del carro
Mostrar el carro
Manel López / @mloptor #DrupalcampSpain2015
/ecommerce.routing.yml
ecommerce.addtocart:
path: '/ecommerce/addtocart/{productId}'
defaults:
_controller: 'DrupalecommerceControllerEcommerceController::addToCart'
_title: 'Add to cart'
requirements:
_permission: 'access content'
ecommerce.removefromcart:
path: '/ecommerce/removefromcart/{productId}'
defaults:
_controller: 'DrupalecommerceControllerEcommerceController::removeFromCart'
_title: 'Remove from cart'
requirements:
_permission: 'access content'
ecommerce.showcart:
path: '/ecommerce/shoppingcart'
defaults:
_controller: 'DrupalecommerceControllerEcommerceController::showCart'
_title: 'Shopping Cart'
requirements:
_permission: 'access content'
Manel López / @mloptor #DrupalcampSpain2015
Adaptadores
Manel López / @mloptor #DrupalcampSpain2015
#Ecommerce application layer
ecommerce.manager:
class: malotorshoppingcartApplicationServiceShoppingCartService
arguments: ['@ecommerce.product_repository','@ecommerce.basket_repository']
/modules/ecommerce/ecommerce.services.yml
Manel López / @mloptor #DrupalcampSpain2015
#Ecommerce application layer
ecommerce.manager:
class: malotorshoppingcartApplicationServiceShoppingCartService
arguments: ['@ecommerce.product_repository','@ecommerce.basket_repository']
/modules/ecommerce/ecommerce.services.yml
Servicio de la capa de aplicación
Manel López / @mloptor #DrupalcampSpain2015
#Ecommerce application layer
ecommerce.manager:
class: malotorshoppingcartApplicationServiceShoppingCartService
arguments: ['@ecommerce.product_repository','@ecommerce.basket_repository']
/modules/ecommerce/ecommerce.services.yml
Inyectar Adaptadores
Manel López / @mloptor #DrupalcampSpain2015
/modules/ecommerce/ecommerce.services.yml
#Ecommerce application layer
ecommerce.manager:
class: malotorshoppingcartApplicationServiceShoppingCartService
arguments: ['@ecommerce.product_repository','@ecommerce.basket_repository']
#Repositories
ecommerce.product_repository:
class: DrupalecommerceAdaptersProductRepository
arguments: ['@ecommerce.coupon_repository']
ecommerce.basket_repository:
class: DrupalecommerceAdaptersBasketRepository
arguments: ['@ecommerce.coupon_repository','@ecommerce.linecart_provider']
Manel López / @mloptor #DrupalcampSpain2015
#Ecommerce application layer
ecommerce.manager:
class: malotorshoppingcartApplicationServiceShoppingCartService
arguments: ['@ecommerce.product_repository','@ecommerce.basket_repository']
#Repositories
ecommerce.product_repository:
class: DrupalecommerceAdaptersProductRepository
arguments: ['@ecommerce.coupon_repository']
ecommerce.basket_repository:
class: DrupalecommerceAdaptersBasketRepository
arguments: ['@ecommerce.coupon_repository','@ecommerce.linecart_provider']
/modules/ecommerce/ecommerce.services.yml
Repositorio Cupones
Manel López / @mloptor #DrupalcampSpain2015
#Ecommerce application layer
ecommerce.manager:
class: malotorshoppingcartApplicationServiceShoppingCartService
arguments: ['@ecommerce.product_repository','@ecommerce.basket_repository']
#Repositories
ecommerce.product_repository:
class: DrupalecommerceAdaptersProductRepository
arguments: ['@ecommerce.coupon_repository']
ecommerce.basket_repository:
class: DrupalecommerceAdaptersBasketRepository
arguments: ['@ecommerce.coupon_repository','@ecommerce.linecart_provider']
/modules/ecommerce/ecommerce.services.yml
Lineas de carro
Manel López / @mloptor #DrupalcampSpain2015
Product
Repository
Product
Entity
Product Adapter
Coupon
Repository
ID Coupon
ID Coupon Node Coupon
Manel López / @mloptor #DrupalcampSpain2015
use malotorshoppingcartApplicationRepositoryProductRepository as
ProductRepositoryInterface;
use malotorshoppingcartApplicationFactoryProductFactory;
class ProductRepository implements ProductRepositoryInterface {
private $couponRepository;
const ENTITY_NAME = "node";
public function __construct($couponRepository) {
$this->couponRepository = $couponRepository;
}
public function get($id) {
$coupon = $this->couponRepository->load($id);
$object = new stdClass();
$object->id = $coupon->id();
$object->price = $coupon->field_price->value;
return ProductFactory::create($object);
}
}
/modules/ecommerce/src/Adapters/ProductRepository.php
Manel López / @mloptor #DrupalcampSpain2015
use malotorshoppingcartApplicationRepositoryProductRepository as
ProductRepositoryInterface;
use malotorshoppingcartApplicationFactoryProductFactory;
class ProductRepository implements ProductRepositoryInterface {
private $couponRepository;
const ENTITY_NAME = "node";
public function __construct($couponRepository) {
$this->couponRepository = $couponRepository;
}
public function get($id) {
$coupon = $this->couponRepository->load($id);
$object = new stdClass();
$object->id = $coupon->id();
$object->price = $coupon->field_price->value;
return ProductFactory::create($object);
}
}
/modules/ecommerce/src/Adapters/ProductRepository.php
Manel López / @mloptor #DrupalcampSpain2015
use malotorshoppingcartApplicationRepositoryProductRepository as
ProductRepositoryInterface;
use malotorshoppingcartApplicationFactoryProductFactory;
class ProductRepository implements ProductRepositoryInterface {
private $couponRepository;
const ENTITY_NAME = "node";
public function __construct($couponRepository) {
$this->couponRepository = $couponRepository;
}
public function get($id) {
$coupon = $this->couponRepository->load($id);
$object = new stdClass();
$object->id = $coupon->id();
$object->price = $coupon->field_price->value;
return ProductFactory::create($object);
}
}
/modules/ecommerce/src/Adapters/ProductRepository.php
Manel López / @mloptor #DrupalcampSpain2015
use malotorshoppingcartApplicationRepositoryProductRepository as
ProductRepositoryInterface;
use malotorshoppingcartApplicationFactoryProductFactory;
class ProductRepository implements ProductRepositoryInterface {
private $couponRepository;
const ENTITY_NAME = "node";
public function __construct($couponRepository) {
$this->couponRepository = $couponRepository;
}
public function get($id) {
$coupon = $this->couponRepository->load($id);
$object = new stdClass();
$object->id = $coupon->id();
$object->price = $coupon->field_price->value;
return ProductFactory::create($object);
}
}
/modules/ecommerce/src/Adapters/ProductRepository.php
Manel López / @mloptor #DrupalcampSpain2015
BasketRepository
Basket
Repository
Basket
Entity
Coupon
Repository
ID Basket
LineCart
Provider
DataBase
ID User
Session
Line Carts
Manel López / @mloptor #DrupalcampSpain2015
Línea de carro
( basket_id , coupon_id , quantity )
Manel López / @mloptor #DrupalcampSpain2015
Línea de carro
( user_id , coupon_id , quantity )
Manel López / @mloptor #DrupalcampSpain2015
/modules/ecommerce/src/Adapters/BasketRepository.php
class BasketRepository implements BasketRepositoryInterface {
public function __construct($couponRepository, $lineCartDataProvider) {
$this->couponRepository = $couponRepository;
$this->dataProvider = $lineCartDataProvider;
}
public function get($baskedId) {
$cartLines = $this->dataProvider->getAll();
$products = array();
foreach ($cartLines as $row) {
$coupon = $this->couponRepository->load($row->item_id);
$object = new stdClass();
$object->id = $row->item_id;
$object->price = $coupon->field_price->value;
$object->quantity = $row->quantity;
$products[] = $object;
}
return BasketFactory::create($products);
}
Manel López / @mloptor #DrupalcampSpain2015
/modules/ecommerce/src/Adapters/BasketRepository.php
class BasketRepository implements BasketRepositoryInterface {
public function __construct($couponRepository, $lineCartDataProvider) {
$this->couponRepository = $couponRepository;
$this->dataProvider = $lineCartDataProvider;
}
public function get($baskedId) {
$cartLines = $this->dataProvider->getAll();
$products = array();
foreach ($cartLines as $row) {
$coupon = $this->couponRepository->load($row->item_id);
$object = new stdClass();
$object->id = $row->item_id;
$object->price = $coupon->field_price->value;
$object->quantity = $row->quantity;
$products[] = $object;
}
return BasketFactory::create($products);
}
Lineas de carro
Manel López / @mloptor #DrupalcampSpain2015
/modules/ecommerce/src/Adapters/BasketRepository.php
class BasketRepository implements BasketRepositoryInterface {
public function __construct($couponRepository, $lineCartDataProvider) {
$this->couponRepository = $couponRepository;
$this->dataProvider = $lineCartDataProvider;
}
public function get($baskedId) {
$cartLines = $this->dataProvider->getAll();
$products = array();
foreach ($cartLines as $row) {
$coupon = $this->couponRepository->load($row->item_id);
$object = new stdClass();
$object->id = $row->item_id;
$object->price = $coupon->field_price->value;
$object->quantity = $row->quantity;
$products[] = $object;
}
return BasketFactory::create($products);
}
Cupón
Manel López / @mloptor #DrupalcampSpain2015
/modules/ecommerce/src/Adapters/BasketRepository.php
class BasketRepository implements BasketRepositoryInterface {
public function __construct($couponRepository, $lineCartDataProvider) {
$this->couponRepository = $couponRepository;
$this->dataProvider = $lineCartDataProvider;
}
public function get($baskedId) {
$cartLines = $this->dataProvider->getAll();
$products = array();
foreach ($cartLines as $row) {
$coupon = $this->couponRepository->load($row->item_id);
$object = new stdClass();
$object->id = $row->item_id;
$object->price = $coupon->field_price->value;
$object->quantity = $row->quantity;
$products[] = $object;
}
return BasketFactory::create($products);
}
Manel López / @mloptor #DrupalcampSpain2015
/modules/ecommerce/src/Adapters/BasketRepository.php
class BasketRepository implements BasketRepositoryInterface {
public function __construct($couponRepository, $lineCartDataProvider) {
$this->couponRepository = $couponRepository;
$this->dataProvider = $lineCartDataProvider;
}
public function get($baskedId) {
$cartLines = $this->dataProvider->getAll();
$products = array();
foreach ($cartLines as $row) {
$coupon = $this->couponRepository->load($row->item_id);
$object = new stdClass();
$object->id = $row->item_id;
$object->price = $coupon->field_price->value;
$object->quantity = $row->quantity;
$products[] = $object;
}
return BasketFactory::create($products);
}
Manel López / @mloptor #DrupalcampSpain2015
Controlador
Manel López / @mloptor #DrupalcampSpain2015
class EcommerceController extends ControllerBase {
public function __construct($shoppingCart, $router, $printer) {
$this->shoppingCart = $shoppingCart;
$this->router = $router;
$this->printer = $printer;
}
public static function create(ContainerInterface $container) {
return new static(
$container->get('ecommerce.shoppingcart),
$container->get('ecommerce.router'),
$container->get('ecommerce.printer')
);
}
/* … */
}
/modules/ecommerce/src/Controller/EcommerceController.php
Servicio de la capa de aplicación
Manel López / @mloptor #DrupalcampSpain2015
class EcommerceController extends ControllerBase {
public function __construct($shoppingCart, $router, $printer) {
$this->shoppingCart = $shoppingCart;
$this->router = $router;
$this->printer = $printer;
}
public static function create(ContainerInterface $container) {
return new static(
$container->get('ecommerce.shoppingcart),
$container->get('ecommerce.router'),
$container->get('ecommerce.printer')
);
}
/* … */
}
/modules/ecommerce/src/Controller/EcommerceController.php
Servicios Auxiliares
Manel López / @mloptor #DrupalcampSpain2015
class EcommerceController extends ControllerBase {
public function addToCart($productId) {
try {
$this->shoppingCart->addProductToBasket($productId, null);
return $this->router->redirectToPreviosPage("Product added");
} catch (Exception $e) {
return $this->router->redirectWithError($e->getMessage());
}
}
}
/modules/ecommerce/src/Controller/EcommerceController.php
Manel López / @mloptor #DrupalcampSpain2015
class EcommerceController extends ControllerBase {
public function addToCart($productId) {
try {
$this->shoppingCart->addProductToBasket($productId, null);
return $this->router->redirectToPreviosPage("Product added");
} catch (Exception $e) {
return $this->router->redirectWithError($e->getMessage());
}
}
}
/modules/ecommerce/src/Controller/EcommerceController.php
Manel López / @mloptor #DrupalcampSpain2015
class EcommerceController extends ControllerBase {
public function addToCart($productId) {
try {
$this->shoppingCart->addProductToBasket($productId, null);
return $this->router->redirectToPreviosPage("Product added");
} catch (Exception $e) {
return $this->router->redirectWithError($e->getMessage());
}
}
}
/modules/ecommerce/src/Controller/EcommerceController.php
Manel López / @mloptor #DrupalcampSpain2015
class EcommerceController extends ControllerBase {
public function removeFromCart($productId) {
try {
$this->shoppingCart->removeProductFromBasket($productId, null);
return $this->router->redirectToPreviosPage("Product removed");
} catch (Exception $e) {
return $this->router->redirectWithError($e->getMessage());
}
}
}
/modules/ecommerce/src/Controller/EcommerceController.php
Manel López / @mloptor #DrupalcampSpain2015
class EcommerceController extends ControllerBase {
public function showCart() {
try {
$this->printer->setDisplay('full');
return $this->printer->render();
} catch (Exception $e) {
return $this->router->redirectWithError($e->getMessage());
}
}
}
/modules/ecommerce/src/Controller/EcommerceController.php
Manel López / @mloptor #DrupalcampSpain2015
/modules/ecommerce/ecommerce.services.yml
ecommerce.printer:
class: DrupalecommerceServicesPrinterService
arguments: ['@ecommerce.shoppingcart']
Manel López / @mloptor #DrupalcampSpain2015
/modules/ecommerce/ecommerce.services.yml
ecommerce.printer:
class: DrupalecommerceServicesPrinterService
arguments: ['@ecommerce.shoppingcart']
Manel López / @mloptor #DrupalcampSpain2015
Bloque “Shopping Cart”
Manel López / @mloptor #DrupalcampSpain2015
/modules/ecommerce/src/Plugin/Block/ShoppingCartBlock.php
/**
* Provides a shooping cart block.
* @Block(
* id = "shoppingcartblock",
* subject = @Translation("Shopping Cart block"),
* admin_label = @Translation("Shopping Cart block")
* )
*/
class ShoppingCartBlock extends BlockBase {
public function build() {
$printer = Drupal::service('ecommerce.printer');
$printer->setDisplay('short');
return $printer->render();
}
}
Manel López / @mloptor #DrupalcampSpain2015
Demo
Manel López / @mloptor #DrupalcampSpain2015
#project_wishlist
Mantenible
Escalable
Fiable
Manel López / @mloptor #DrupalcampSpain2015
“There are no
dumb
questions”
Manel López / @mloptor #DrupalcampSpain2015
Referencias
Hexagonal architecture - Alistair Cockburn
http://alistair.cockburn.us/Hexagonal+architecture
Desdrupalizando la lógica de negocio - Carles Climent
https://vimeo.com/112570854
The Clean Architecture - Robert C. Martin
http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html
What is Hexagonal architecture - Philip Brown
http://culttt.com/2014/12/31/hexagonal-architecture/
Manel López / @mloptor #DrupalcampSpain2015
Referencias
Hexagonal Architecture - Chris Fidao
http://userscape.com/laracon/2014/chrisfidao.html
http://fideloper.com/hexagonal-architecture
Hexagonal Architecture - Carlos Buenosvinos
http://www.slideshare.net/carlosbuenosvinos/hexagonal-architecture-php-
barcelona
Manel López / @mloptor #DrupalcampSpain2015
Gracias!
… para Noa López Llombart

More Related Content

Similar to Drupal 8 Desacoplar la lógica de negocio del framework

Introduction to Drupal 7 Panels - Drupalcamp Spain 2015
Introduction to Drupal 7 Panels - Drupalcamp Spain 2015Introduction to Drupal 7 Panels - Drupalcamp Spain 2015
Introduction to Drupal 7 Panels - Drupalcamp Spain 2015Joeri Poesen
 
Drupal Modules
Drupal ModulesDrupal Modules
Drupal ModulesRyan Cross
 
From Drupal 7 to Drupal 8 - Drupal Intensive Course Overview
From Drupal 7 to Drupal 8 - Drupal Intensive Course OverviewFrom Drupal 7 to Drupal 8 - Drupal Intensive Course Overview
From Drupal 7 to Drupal 8 - Drupal Intensive Course OverviewItalo Mairo
 
HTML5 Drupal Working Group
HTML5 Drupal Working GroupHTML5 Drupal Working Group
HTML5 Drupal Working GroupJen Simmons
 
Building a multilingual & multi-country e-commerce site with Drupal 7 @ NYC C...
Building a multilingual & multi-country e-commerce site with Drupal 7 @ NYC C...Building a multilingual & multi-country e-commerce site with Drupal 7 @ NYC C...
Building a multilingual & multi-country e-commerce site with Drupal 7 @ NYC C...valcker
 
Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8
Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8
Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8Acquia
 
Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8
Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8
Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8Jake Borr
 
Multiple countries & multilingual e-commerce platforms using Drupal
Multiple countries & multilingual e-commerce platforms using DrupalMultiple countries & multilingual e-commerce platforms using Drupal
Multiple countries & multilingual e-commerce platforms using DrupalAdyax
 
The State of Drupal 8
The State of Drupal 8The State of Drupal 8
The State of Drupal 8nyccamp
 
Start Here: How to Prepare for Your Drupal 8 Journey
Start Here: How to Prepare for Your Drupal 8 JourneyStart Here: How to Prepare for Your Drupal 8 Journey
Start Here: How to Prepare for Your Drupal 8 JourneyAcquia
 
Drupalcon prague v4
Drupalcon prague v4Drupalcon prague v4
Drupalcon prague v4Mike Lamb
 
Implementing a Symfony Based CMS in a Publishing Company
Implementing a Symfony Based CMS in a Publishing CompanyImplementing a Symfony Based CMS in a Publishing Company
Implementing a Symfony Based CMS in a Publishing CompanyMarcos Labad
 
Building Drupal 8 Sites
Building Drupal 8 SitesBuilding Drupal 8 Sites
Building Drupal 8 SitesExove
 
State of Drupal keynote, DrupalCon Vienna
State of Drupal keynote, DrupalCon ViennaState of Drupal keynote, DrupalCon Vienna
State of Drupal keynote, DrupalCon ViennaDries Buytaert
 
Drupal content editor flexibility
Drupal content editor flexibilityDrupal content editor flexibility
Drupal content editor flexibilityhernanibf
 
Drupal 8 as a Drop-In Content Engine - SymfonyLive Berlin 2015
Drupal 8 as a Drop-In Content Engine - SymfonyLive Berlin 2015Drupal 8 as a Drop-In Content Engine - SymfonyLive Berlin 2015
Drupal 8 as a Drop-In Content Engine - SymfonyLive Berlin 2015Jeffrey McGuire
 
Functional FIPS: Learning PHP for Drupal Theming
Functional FIPS: Learning PHP for Drupal ThemingFunctional FIPS: Learning PHP for Drupal Theming
Functional FIPS: Learning PHP for Drupal ThemingEmma Jane Hogbin Westby
 
Legacy Code: Evolve or Rewrite?
Legacy Code: Evolve or Rewrite?Legacy Code: Evolve or Rewrite?
Legacy Code: Evolve or Rewrite?Cyrille Martraire
 
MadridDevOps January 2015: "DevOps 101, one intro to devops"
MadridDevOps January 2015: "DevOps 101, one intro to devops"MadridDevOps January 2015: "DevOps 101, one intro to devops"
MadridDevOps January 2015: "DevOps 101, one intro to devops"Antonio Peña
 

Similar to Drupal 8 Desacoplar la lógica de negocio del framework (20)

Introduction to Drupal 7 Panels - Drupalcamp Spain 2015
Introduction to Drupal 7 Panels - Drupalcamp Spain 2015Introduction to Drupal 7 Panels - Drupalcamp Spain 2015
Introduction to Drupal 7 Panels - Drupalcamp Spain 2015
 
Drupal Modules
Drupal ModulesDrupal Modules
Drupal Modules
 
From Drupal 7 to Drupal 8 - Drupal Intensive Course Overview
From Drupal 7 to Drupal 8 - Drupal Intensive Course OverviewFrom Drupal 7 to Drupal 8 - Drupal Intensive Course Overview
From Drupal 7 to Drupal 8 - Drupal Intensive Course Overview
 
HTML5 Drupal Working Group
HTML5 Drupal Working GroupHTML5 Drupal Working Group
HTML5 Drupal Working Group
 
Building a multilingual & multi-country e-commerce site with Drupal 7 @ NYC C...
Building a multilingual & multi-country e-commerce site with Drupal 7 @ NYC C...Building a multilingual & multi-country e-commerce site with Drupal 7 @ NYC C...
Building a multilingual & multi-country e-commerce site with Drupal 7 @ NYC C...
 
Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8
Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8
Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8
 
Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8
Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8
Drupal Console Deep Dive: How to Develop Faster and Smarter on Drupal 8
 
Multiple countries & multilingual e-commerce platforms using Drupal
Multiple countries & multilingual e-commerce platforms using DrupalMultiple countries & multilingual e-commerce platforms using Drupal
Multiple countries & multilingual e-commerce platforms using Drupal
 
The State of Drupal 8
The State of Drupal 8The State of Drupal 8
The State of Drupal 8
 
Start Here: How to Prepare for Your Drupal 8 Journey
Start Here: How to Prepare for Your Drupal 8 JourneyStart Here: How to Prepare for Your Drupal 8 Journey
Start Here: How to Prepare for Your Drupal 8 Journey
 
Drupalcon prague v4
Drupalcon prague v4Drupalcon prague v4
Drupalcon prague v4
 
Implementing a Symfony Based CMS in a Publishing Company
Implementing a Symfony Based CMS in a Publishing CompanyImplementing a Symfony Based CMS in a Publishing Company
Implementing a Symfony Based CMS in a Publishing Company
 
Building Drupal 8 Sites
Building Drupal 8 SitesBuilding Drupal 8 Sites
Building Drupal 8 Sites
 
State of Drupal keynote, DrupalCon Vienna
State of Drupal keynote, DrupalCon ViennaState of Drupal keynote, DrupalCon Vienna
State of Drupal keynote, DrupalCon Vienna
 
Drupal content editor flexibility
Drupal content editor flexibilityDrupal content editor flexibility
Drupal content editor flexibility
 
David Rodriguez - davidjguru CV 2024 updated
David Rodriguez - davidjguru CV 2024 updatedDavid Rodriguez - davidjguru CV 2024 updated
David Rodriguez - davidjguru CV 2024 updated
 
Drupal 8 as a Drop-In Content Engine - SymfonyLive Berlin 2015
Drupal 8 as a Drop-In Content Engine - SymfonyLive Berlin 2015Drupal 8 as a Drop-In Content Engine - SymfonyLive Berlin 2015
Drupal 8 as a Drop-In Content Engine - SymfonyLive Berlin 2015
 
Functional FIPS: Learning PHP for Drupal Theming
Functional FIPS: Learning PHP for Drupal ThemingFunctional FIPS: Learning PHP for Drupal Theming
Functional FIPS: Learning PHP for Drupal Theming
 
Legacy Code: Evolve or Rewrite?
Legacy Code: Evolve or Rewrite?Legacy Code: Evolve or Rewrite?
Legacy Code: Evolve or Rewrite?
 
MadridDevOps January 2015: "DevOps 101, one intro to devops"
MadridDevOps January 2015: "DevOps 101, one intro to devops"MadridDevOps January 2015: "DevOps 101, one intro to devops"
MadridDevOps January 2015: "DevOps 101, one intro to devops"
 

Drupal 8 Desacoplar la lógica de negocio del framework

  • 1. Manel López / @mloptor #DrupalcampSpain2015 Desacoplar la lógica de negocio del framework
  • 2. Manel López / @mloptor #DrupalcampSpain2015 Manel López Torrent Ingeniero en informática Web Developer @mloptor malotor@gmail.com
  • 3. Manel López / @mloptor #DrupalcampSpain2015 Desacoplada = Independiente
  • 4. Manel López / @mloptor #DrupalcampSpain2015 “El dominio es el mundo del negocio” Ideas, conocimiento y datos del problema que intentamos resolver. Expertos del dominio Dominio
  • 5. Manel López / @mloptor #DrupalcampSpain2015 Objeto del negocio Objetos o entidades abstractas que conforman el dominio.
  • 6. Manel López / @mloptor #DrupalcampSpain2015 UniversidadPeriódico Profesor Alumno Asignatura Departament o Editor Periodista Noticia Anuncio
  • 7. Manel López / @mloptor #DrupalcampSpain2015 Reglas de negocio
  • 8. Manel López / @mloptor #DrupalcampSpain2015 Modelo del dominio Nuestra solución al problema Conocimiento estructurado del problema Código, prosa o un diagrama. Refleja el conocimiento que tenemos del dominio
  • 9. Manel López / @mloptor #DrupalcampSpain2015 Lógica de negocio / Capa de negocio “La parte de nuestro software que mantiene el modelo del dominio y codifica las reglas de negocio”
  • 10. Manel López / @mloptor #DrupalcampSpain2015 Aplicaciones web y Frameworks
  • 11. Manel López / @mloptor #DrupalcampSpain2015 ¿Dónde se implementa la lógica de negocio?
  • 12. Manel López / @mloptor #DrupalcampSpain2015 ControladorPetición Modelo Vista Persistencia de datos Respuesta Presentación de los datos
  • 13. Manel López / @mloptor #DrupalcampSpain2015 ControladorPetición Modelo Vista Persistencia de datos Respuesta Lógica de negocio Presentación de los datos
  • 14. Manel López / @mloptor #DrupalcampSpain2015 Problemas Alto nivel de dependencia Difícil testeo No SRP HTTP Tamaño
  • 15. Manel López / @mloptor #DrupalcampSpain2015 Hi! I'm your controller
  • 16. Manel López / @mloptor #DrupalcampSpain2015 Controlador Lógica de negocio ?
  • 17. Manel López / @mloptor #DrupalcampSpain2015 Arquitectura hexagonal
  • 18. Manel López / @mloptor #DrupalcampSpain2015 Arquitectura hexagonal Arquitectura de software Alistair CockBurn AKA “Ports and adapters” Separar responsabilidades
  • 19. Manel López / @mloptor #DrupalcampSpain2015 Domain Model Lógica de negocio
  • 20. Manel López / @mloptor #DrupalcampSpain2015 Application Layer Domain Model API
  • 21. Manel López / @mloptor #DrupalcampSpain2015 Application Layer Domain Model Puerto Puerto Puerto Puerto
  • 22. Manel López / @mloptor #DrupalcampSpain2015 Application Layer Domain Model Infrastructure Layer Adaptador
  • 23. Manel López / @mloptor #DrupalcampSpain2015 Lógica de negocio Framework
  • 24. Manel López / @mloptor #DrupalcampSpain2015 Puertos/Adaptadores
  • 25. Manel López / @mloptor #DrupalcampSpain2015 interface log { public function message($message); public function error($message); } class LogService implements log { public function __construct($factory) { $this->loggerFactory = $factory; } public function message($message) { $this->loggerFactory->get('my_module')->error($message); } public function error($message) { $this->loggerFactory->get('my_module')->error($message); } } Adaptador Puerto
  • 26. Manel López / @mloptor #DrupalcampSpain2015 Application Layer Domain Model
  • 27. Manel López / @mloptor #DrupalcampSpain2015 Application Layer Domain Model
  • 28. Manel López / @mloptor #DrupalcampSpain2015 Application Layer Domain Model
  • 29. Manel López / @mloptor #DrupalcampSpain2015 Application Layer Domain Model
  • 30. Manel López / @mloptor #DrupalcampSpain2015 Fronteras
  • 31. Manel López / @mloptor #DrupalcampSpain2015 Application Layer Domain Model Infrastructure Layer
  • 32. Manel López / @mloptor #DrupalcampSpain2015 Ventajas
  • 33. Manel López / @mloptor #DrupalcampSpain2015 Agnóstica
  • 34. Manel López / @mloptor #DrupalcampSpain2015 Fácil Testeo
  • 35. Manel López / @mloptor #DrupalcampSpain2015 Aislada Cambios aislados + refactoring _ deuda técnica
  • 36. Manel López / @mloptor #DrupalcampSpain2015 Patrones de diseño
  • 37. Manel López / @mloptor #DrupalcampSpain2015 Entidad Representan a los objetos del dominio Estado + Comportamiento Contener lógica del negocio Modelo Anémico Identidad
  • 38. Manel López / @mloptor #DrupalcampSpain2015 Adaptador Adaptadores Puertos
  • 39. Manel López / @mloptor #DrupalcampSpain2015 Factorías stdClass new Product()
  • 40. Manel López / @mloptor #DrupalcampSpain2015 Repositorios RepositorioCliente DataProvider Factoría 1 - Query 2 - Query 3 - Object Data 4 - Object Data 5 - Real Object6 - Real Object
  • 41. Manel López / @mloptor #DrupalcampSpain2015 Fachada
  • 42. Manel López / @mloptor #DrupalcampSpain2015 Esto no es el Santo Grial
  • 43. Manel López / @mloptor #DrupalcampSpain2015 Escalabilidad Mantenibilidad fiabilidad Complejidad
  • 44. Manel López / @mloptor #DrupalcampSpain2015 CRUD Mínima lógica Complejidad Oculta Escenarios Inciertos Requisitos cambiantes Desconocimiento del Dominio
  • 45. Manel López / @mloptor #DrupalcampSpain2015 SHOW ME THE CODE!!!
  • 46. Manel López / @mloptor #DrupalcampSpain2015
  • 47. Manel López / @mloptor #DrupalcampSpain2015 Shopping Cart 1. CLIENTE Quiero ver un listado de productos para elegir lo que quiero comprar 2. CLIENTE Quiero ser capaz de elegir el producto que quiero comprar y añadirlo a mi cesta de la compra a. Si un producto está ya en la cesta incrementaremos la cantidad 3. CLIENTE Quiero ser capaz de eliminar productos de mi cesta si he cambiado de opinión 4. CLIENTE Quero poder ver el coste total de todos los productos de mi cesta en todo momento. 5. CLIENTE Quiero que se guarde mi cesta de la compra por si quiero pensarlo mejor
  • 48. Manel López / @mloptor #DrupalcampSpain2015 Repositorios de código Shopping Cart Business Logic https://github.com/malotor/basket Shopping Cart Drupal 8 module https://github.com/malotor/drupal_ecommerce
  • 49. Manel López / @mloptor #DrupalcampSpain2015 Modelo del dominio Diseño emergente
  • 50. Manel López / @mloptor #DrupalcampSpain2015 Tests unitarios Tests integración
  • 51. Manel López / @mloptor #DrupalcampSpain2015 Modelo del dominio
  • 52. Manel López / @mloptor #DrupalcampSpain2015 Basket Products contains *
  • 53. Manel López / @mloptor #DrupalcampSpain2015 src ├── Application │ ├── Factory │ │ ├── BasketFactory.php │ │ └── ProductFactory.php │ ├── Repository │ │ ├── BasketRepository.php │ │ └── ProductRepository.php │ └── Service │ └── ShoppingCartService.php └── Domain └── Entity ├── Basket.php ├── Item.php └── Product.php Capa Aplicación Capa Dominio
  • 54. Manel López / @mloptor #DrupalcampSpain2015 Capa Domino - Entidades
  • 55. Manel López / @mloptor #DrupalcampSpain2015 /src/Domain/Entity/Product.php class Product implements Item { private $id; private $quantity; private $price; public function __construct($id, $price, $quantity = 1) {} public function getId() {} public function getQuantity() {} public function getAmount() {} public function increaseQuantity() {} }
  • 56. Manel López / @mloptor #DrupalcampSpain2015 /src/Domain/Entity/Product.php class Product implements Item { private $id; private $quantity; private $price; public function __construct($id, $price, $quantity = 1) {} public function getId() {} public function getQuantity() {} public function getAmount() {} public function increaseQuantity() {} } Estado Comportamiento
  • 57. Manel López / @mloptor #DrupalcampSpain2015 /src/Domain/Entity/Basket.php class Basket { private $items = []; public function countItems() {} public function addItem(Item $item) {} public function getItem($itemId) {} public function removeItem($itemId){} public function getItems(){} public function totalAmount(){} }
  • 58. Manel López / @mloptor #DrupalcampSpain2015 /src/Domain/Entity/Basket.php class Basket { public function addItem(Item $item) { $itemId = $item->getId(); if ($this->containsItem($itemId)) $this->items[$itemId]->increaseQuantity(); else $this->items[$itemId] = $item; } } “Si un producto ya existe en el carro incrementaremos su cantidad”
  • 59. Manel López / @mloptor #DrupalcampSpain2015 Capa Dominio - Test de integración
  • 60. Manel López / @mloptor #DrupalcampSpain2015 /tests/Integration/Basket.php class BasketTest extends PHPUnit_Framework_TestCase { protected $basket; public function setUp() { $this->products[0] = new Product(1,10); $this->products[1] = new Product(2,5.5); $this->basket = new Basket(); } public function testAddAProduct() { $this->assertEquals(0, $this->basket->countItems()); $this->basket->addItem($this->products[0]); $this->assertEquals(1, $this->basket->countItems()); } }
  • 61. Manel López / @mloptor #DrupalcampSpain2015 /tests/Integration/Basket.php class BasketTest extends PHPUnit_Framework_TestCase { public function testRemoveAProduct() { $this->basket->addItem($this->products[0]); $this->basket->addItem($this->products[1]); $this->basket->removeItem(1); $this->assertEquals(1, $this->basket->countItems()); } public function testIncreaseProductQuantity() { $this->assertEquals(1, $this->products[0]->getQuantity()); $this->basket->addItem($this->products[0]); $this->basket->addItem($this->products[0]); $this->assertEquals(1, $this->basket->countItems()); $this->assertEquals(2, $this->products[0]->getQuantity()); } }
  • 62. Manel López / @mloptor #DrupalcampSpain2015 /tests/Integration/Basket.php class BasketTest extends PHPUnit_Framework_TestCase { public function testCartTotalAmount() { $this->basket->addItem($this->products[0]); $this->basket->addItem($this->products[0]); $this->basket->addItem($this->products[1]); $this->assertEquals(25.5, $this->basket->totalAmount()); } }
  • 63. Manel López / @mloptor #DrupalcampSpain2015 Capa Aplicación - Servicios
  • 64. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Service/ShoppingCartService.php use malotorshoppingcartApplicationRepositoryProductRepository; use malotorshoppingcartApplicationRepositoryBasketRepository; class ShoppingCartService { private $productRepository; private $basketRepository; public function __construct( ProductRepository $productRepository, BasketRepository $basketRepository ) {...} }
  • 65. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Service/ShoppingCartService.php use malotorshoppingcartApplicationRepositoryProductRepository; use malotorshoppingcartApplicationRepositoryBasketRepository; class ShoppingCartService { private $productRepository; private $basketRepository; public function __construct( ProductRepository $productRepository, BasketRepository $basketRepository ) {...} } Puertos
  • 66. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Service/ShoppingCartService.php use malotorshoppingcartApplicationRepositoryProductRepository; use malotorshoppingcartApplicationRepositoryBasketRepository; class ShoppingCartService { private $productRepository; private $basketRepository; public function __construct( ProductRepository $productRepository, BasketRepository $basketRepository ) {...} } Interfaces
  • 67. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Service/ShoppingCartService.php class ShoppingCartService { {...} public function addProductToBasket($productId, $baskedId) {...} public function removeProductFromBasket($productId, $basketId) {...} public function getProductsFromBasket($basketId) {...} public function getBasketTotalAmount($basketId) {...} } API para Infraestructura
  • 68. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Service/ShoppingCartService.php class ShoppingCartService { public function addProductToBasket($productId, $baskedId) { $basket = $this->basketRepository->get($baskedId); $product = $this->productRepository->get($productId); $basket->addItem($product); $this->basketRepository->save($basket); } {...} }
  • 69. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Service/ShoppingCartService.php class ShoppingCartService { public function addProductToBasket($productId, $baskedId) { $basket = $this->basketRepository->get($baskedId); $product = $this->productRepository->get($productId); $basket->addItem($product); $this->basketRepository->save($basket); } {...} } Recupera datos al los repositorios
  • 70. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Service/ShoppingCartService.php class ShoppingCartService { public function addProductToBasket($productId, $baskedId) { $basket = $this->basketRepository->get($baskedId); $product = $this->productRepository->get($productId); $basket->addItem($product); $this->basketRepository->save($basket); } {...} } Ejecutará la lógica de negocio
  • 71. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Service/ShoppingCartService.php class ShoppingCartService { public function addProductToBasket($productId, $baskedId) { $basket = $this->basketRepository->get($baskedId); $product = $this->productRepository->get($productId); $basket->addItem($product); $this->basketRepository->save($basket); } {...} } Persiste los datos
  • 72. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Service/ShoppingCartService.php class ShoppingCartService { public function addProductToBasket($productId, $baskedId) { $basket = $this->basketRepository->get($baskedId); $product = $this->productRepository->get($productId); $basket->addItem($product); $this->basketRepository->save($basket); } {...} } Patrón Fachada
  • 73. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Service/ShoppingCartService.php class ShoppingCartService { public function removeProductFromBasket($productId, $basketId) { $basket = $this->basketRepository->get($basketId); $basket->removeItem($productId); $this->basketRepository->save($basket); } public function getProductsFromBasket($basketId) { $basket = $this->basketRepository->get($basketId); return $basket->getItems(); } public function getBasketTotalAmount($basketId) { $basket = $this->basketRepository->get($basketId); return $basket->totalAmount(); } }
  • 74. Manel López / @mloptor #DrupalcampSpain2015 Capa Aplicación - Repositorios (Puertos)
  • 75. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Repository/BasketRepository.php namespace malotorshoppingcartApplicationRepository; use malotorshoppingcartDomainEntityBasket; interface BasketRepository { /** * @return Basket */ public function get($basketId); public function save(Basket $basket); }
  • 76. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Repository/BasketRepository.php namespace malotorshoppingcartApplicationRepository; use malotorshoppingcartDomainEntityBasket; interface BasketRepository { /** * @return Basket */ public function get($basketId); public function save(Basket $basket); }
  • 77. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Repository/ProductRepository.php namespace malotorshoppingcartApplicationRepository; use malotorshoppingcartDomainEntityProduct; interface ProductRepository { /** * @return Product */ public function get($productId); }
  • 78. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Repository/ProductRepository.php namespace malotorshoppingcartApplicationRepository; use malotorshoppingcartDomainEntityProduct; interface ProductRepository { /** * @return Product */ public function get($productId); }
  • 79. Manel López / @mloptor #DrupalcampSpain2015 Capa Aplicación - Factorías
  • 80. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Factory/Product.php use malotorshoppingcartDomainEntityProduct; class ProductFactory { static public function create($object) { return new Product( $object->id, $object->price, $object->quantity ); } }
  • 81. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Factory/Product.php use malotorshoppingcartDomainEntityProduct; class ProductFactory { static public function create($object) { return new Product( $object->id, $object->price, $object->quantity ); } } stdClass new Product()
  • 82. Manel López / @mloptor #DrupalcampSpain2015 /src/Application/Factory/Basket.php use malotorshoppingcartDomainEntityBasket; class BasketFactory { static public function create($products = array()) { $basket = new Basket(); foreach ($products as $object) { $basket->addItem( ProductFactory::create($object) ); } return $basket; } }
  • 83. Manel López / @mloptor #DrupalcampSpain2015 Empaquetar y distribuir
  • 84. Manel López / @mloptor #DrupalcampSpain2015 /composer.json { "name": "malotor/shoppingcart", "description": "Shopping Cart", "version" : "2.0.0-dev", "require-dev": { "phpunit/phpunit": "~4.4", "phpspec/phpspec": "~2.0" }, "authors": [ { "name": "Manel López", "email": "malotor@gmail.com" } ], "require": { "php": ">=5.4.0" }, "autoload": { "psr-4": { "malotorshoppingcart": "src" } } } https://packagist.org/
  • 85. Manel López / @mloptor #DrupalcampSpain2015 Infraestructura
  • 86. Manel López / @mloptor #DrupalcampSpain2015
  • 87. Manel López / @mloptor #DrupalcampSpain2015 Módulo Drupal 8 $ drupal generate:module
  • 88. Manel López / @mloptor #DrupalcampSpain2015 Cargar la lógica
  • 89. Manel López / @mloptor #DrupalcampSpain2015 /modules/ecommerce/composer.json { "name": "malotor/drupal8_shoppingcart", "description": "Example module", "type": "drupal-module", "repositories": [ { "type": "vcs", "url": "https://github.com/malotor/basket.git" } ], "require": { "malotor/shoppingcart": "dev-master" } }
  • 90. Manel López / @mloptor #DrupalcampSpain2015 Cargamos las dependencias ( composer_manager) $ drush dl composer_manager $ drush en -y composer_manager $ drush composer-manager-init $ cd core $ composer drupal-update
  • 91. Manel López / @mloptor #DrupalcampSpain2015 Rutas
  • 92. Manel López / @mloptor #DrupalcampSpain2015 /ecommerce.routing.yml ecommerce.addtocart: path: '/ecommerce/addtocart/{productId}' defaults: _controller: 'DrupalecommerceControllerEcommerceController::addToCart' _title: 'Add to cart' requirements: _permission: 'access content' ecommerce.removefromcart: path: '/ecommerce/removefromcart/{productId}' defaults: _controller: 'DrupalecommerceControllerEcommerceController::removeFromCart' _title: 'Remove from cart' requirements: _permission: 'access content' ecommerce.showcart: path: '/ecommerce/shoppingcart' defaults: _controller: 'DrupalecommerceControllerEcommerceController::showCart' _title: 'Shopping Cart' requirements: _permission: 'access content'
  • 93. Manel López / @mloptor #DrupalcampSpain2015 /ecommerce.routing.yml ecommerce.addtocart: path: '/ecommerce/addtocart/{productId}' defaults: _controller: 'DrupalecommerceControllerEcommerceController::addToCart' _title: 'Add to cart' requirements: _permission: 'access content' ecommerce.removefromcart: path: '/ecommerce/removefromcart/{productId}' defaults: _controller: 'DrupalecommerceControllerEcommerceController::removeFromCart' _title: 'Remove from cart' requirements: _permission: 'access content' ecommerce.showcart: path: '/ecommerce/shoppingcart' defaults: _controller: 'DrupalecommerceControllerEcommerceController::showCart' _title: 'Shopping Cart' requirements: _permission: 'access content' Añadir al carro Eliminar del carro Mostrar el carro
  • 94. Manel López / @mloptor #DrupalcampSpain2015 /ecommerce.routing.yml ecommerce.addtocart: path: '/ecommerce/addtocart/{productId}' defaults: _controller: 'DrupalecommerceControllerEcommerceController::addToCart' _title: 'Add to cart' requirements: _permission: 'access content' ecommerce.removefromcart: path: '/ecommerce/removefromcart/{productId}' defaults: _controller: 'DrupalecommerceControllerEcommerceController::removeFromCart' _title: 'Remove from cart' requirements: _permission: 'access content' ecommerce.showcart: path: '/ecommerce/shoppingcart' defaults: _controller: 'DrupalecommerceControllerEcommerceController::showCart' _title: 'Shopping Cart' requirements: _permission: 'access content'
  • 95. Manel López / @mloptor #DrupalcampSpain2015 Adaptadores
  • 96. Manel López / @mloptor #DrupalcampSpain2015 #Ecommerce application layer ecommerce.manager: class: malotorshoppingcartApplicationServiceShoppingCartService arguments: ['@ecommerce.product_repository','@ecommerce.basket_repository'] /modules/ecommerce/ecommerce.services.yml
  • 97. Manel López / @mloptor #DrupalcampSpain2015 #Ecommerce application layer ecommerce.manager: class: malotorshoppingcartApplicationServiceShoppingCartService arguments: ['@ecommerce.product_repository','@ecommerce.basket_repository'] /modules/ecommerce/ecommerce.services.yml Servicio de la capa de aplicación
  • 98. Manel López / @mloptor #DrupalcampSpain2015 #Ecommerce application layer ecommerce.manager: class: malotorshoppingcartApplicationServiceShoppingCartService arguments: ['@ecommerce.product_repository','@ecommerce.basket_repository'] /modules/ecommerce/ecommerce.services.yml Inyectar Adaptadores
  • 99. Manel López / @mloptor #DrupalcampSpain2015 /modules/ecommerce/ecommerce.services.yml #Ecommerce application layer ecommerce.manager: class: malotorshoppingcartApplicationServiceShoppingCartService arguments: ['@ecommerce.product_repository','@ecommerce.basket_repository'] #Repositories ecommerce.product_repository: class: DrupalecommerceAdaptersProductRepository arguments: ['@ecommerce.coupon_repository'] ecommerce.basket_repository: class: DrupalecommerceAdaptersBasketRepository arguments: ['@ecommerce.coupon_repository','@ecommerce.linecart_provider']
  • 100. Manel López / @mloptor #DrupalcampSpain2015 #Ecommerce application layer ecommerce.manager: class: malotorshoppingcartApplicationServiceShoppingCartService arguments: ['@ecommerce.product_repository','@ecommerce.basket_repository'] #Repositories ecommerce.product_repository: class: DrupalecommerceAdaptersProductRepository arguments: ['@ecommerce.coupon_repository'] ecommerce.basket_repository: class: DrupalecommerceAdaptersBasketRepository arguments: ['@ecommerce.coupon_repository','@ecommerce.linecart_provider'] /modules/ecommerce/ecommerce.services.yml Repositorio Cupones
  • 101. Manel López / @mloptor #DrupalcampSpain2015 #Ecommerce application layer ecommerce.manager: class: malotorshoppingcartApplicationServiceShoppingCartService arguments: ['@ecommerce.product_repository','@ecommerce.basket_repository'] #Repositories ecommerce.product_repository: class: DrupalecommerceAdaptersProductRepository arguments: ['@ecommerce.coupon_repository'] ecommerce.basket_repository: class: DrupalecommerceAdaptersBasketRepository arguments: ['@ecommerce.coupon_repository','@ecommerce.linecart_provider'] /modules/ecommerce/ecommerce.services.yml Lineas de carro
  • 102. Manel López / @mloptor #DrupalcampSpain2015 Product Repository Product Entity Product Adapter Coupon Repository ID Coupon ID Coupon Node Coupon
  • 103. Manel López / @mloptor #DrupalcampSpain2015 use malotorshoppingcartApplicationRepositoryProductRepository as ProductRepositoryInterface; use malotorshoppingcartApplicationFactoryProductFactory; class ProductRepository implements ProductRepositoryInterface { private $couponRepository; const ENTITY_NAME = "node"; public function __construct($couponRepository) { $this->couponRepository = $couponRepository; } public function get($id) { $coupon = $this->couponRepository->load($id); $object = new stdClass(); $object->id = $coupon->id(); $object->price = $coupon->field_price->value; return ProductFactory::create($object); } } /modules/ecommerce/src/Adapters/ProductRepository.php
  • 104. Manel López / @mloptor #DrupalcampSpain2015 use malotorshoppingcartApplicationRepositoryProductRepository as ProductRepositoryInterface; use malotorshoppingcartApplicationFactoryProductFactory; class ProductRepository implements ProductRepositoryInterface { private $couponRepository; const ENTITY_NAME = "node"; public function __construct($couponRepository) { $this->couponRepository = $couponRepository; } public function get($id) { $coupon = $this->couponRepository->load($id); $object = new stdClass(); $object->id = $coupon->id(); $object->price = $coupon->field_price->value; return ProductFactory::create($object); } } /modules/ecommerce/src/Adapters/ProductRepository.php
  • 105. Manel López / @mloptor #DrupalcampSpain2015 use malotorshoppingcartApplicationRepositoryProductRepository as ProductRepositoryInterface; use malotorshoppingcartApplicationFactoryProductFactory; class ProductRepository implements ProductRepositoryInterface { private $couponRepository; const ENTITY_NAME = "node"; public function __construct($couponRepository) { $this->couponRepository = $couponRepository; } public function get($id) { $coupon = $this->couponRepository->load($id); $object = new stdClass(); $object->id = $coupon->id(); $object->price = $coupon->field_price->value; return ProductFactory::create($object); } } /modules/ecommerce/src/Adapters/ProductRepository.php
  • 106. Manel López / @mloptor #DrupalcampSpain2015 use malotorshoppingcartApplicationRepositoryProductRepository as ProductRepositoryInterface; use malotorshoppingcartApplicationFactoryProductFactory; class ProductRepository implements ProductRepositoryInterface { private $couponRepository; const ENTITY_NAME = "node"; public function __construct($couponRepository) { $this->couponRepository = $couponRepository; } public function get($id) { $coupon = $this->couponRepository->load($id); $object = new stdClass(); $object->id = $coupon->id(); $object->price = $coupon->field_price->value; return ProductFactory::create($object); } } /modules/ecommerce/src/Adapters/ProductRepository.php
  • 107. Manel López / @mloptor #DrupalcampSpain2015 BasketRepository Basket Repository Basket Entity Coupon Repository ID Basket LineCart Provider DataBase ID User Session Line Carts
  • 108. Manel López / @mloptor #DrupalcampSpain2015 Línea de carro ( basket_id , coupon_id , quantity )
  • 109. Manel López / @mloptor #DrupalcampSpain2015 Línea de carro ( user_id , coupon_id , quantity )
  • 110. Manel López / @mloptor #DrupalcampSpain2015 /modules/ecommerce/src/Adapters/BasketRepository.php class BasketRepository implements BasketRepositoryInterface { public function __construct($couponRepository, $lineCartDataProvider) { $this->couponRepository = $couponRepository; $this->dataProvider = $lineCartDataProvider; } public function get($baskedId) { $cartLines = $this->dataProvider->getAll(); $products = array(); foreach ($cartLines as $row) { $coupon = $this->couponRepository->load($row->item_id); $object = new stdClass(); $object->id = $row->item_id; $object->price = $coupon->field_price->value; $object->quantity = $row->quantity; $products[] = $object; } return BasketFactory::create($products); }
  • 111. Manel López / @mloptor #DrupalcampSpain2015 /modules/ecommerce/src/Adapters/BasketRepository.php class BasketRepository implements BasketRepositoryInterface { public function __construct($couponRepository, $lineCartDataProvider) { $this->couponRepository = $couponRepository; $this->dataProvider = $lineCartDataProvider; } public function get($baskedId) { $cartLines = $this->dataProvider->getAll(); $products = array(); foreach ($cartLines as $row) { $coupon = $this->couponRepository->load($row->item_id); $object = new stdClass(); $object->id = $row->item_id; $object->price = $coupon->field_price->value; $object->quantity = $row->quantity; $products[] = $object; } return BasketFactory::create($products); } Lineas de carro
  • 112. Manel López / @mloptor #DrupalcampSpain2015 /modules/ecommerce/src/Adapters/BasketRepository.php class BasketRepository implements BasketRepositoryInterface { public function __construct($couponRepository, $lineCartDataProvider) { $this->couponRepository = $couponRepository; $this->dataProvider = $lineCartDataProvider; } public function get($baskedId) { $cartLines = $this->dataProvider->getAll(); $products = array(); foreach ($cartLines as $row) { $coupon = $this->couponRepository->load($row->item_id); $object = new stdClass(); $object->id = $row->item_id; $object->price = $coupon->field_price->value; $object->quantity = $row->quantity; $products[] = $object; } return BasketFactory::create($products); } Cupón
  • 113. Manel López / @mloptor #DrupalcampSpain2015 /modules/ecommerce/src/Adapters/BasketRepository.php class BasketRepository implements BasketRepositoryInterface { public function __construct($couponRepository, $lineCartDataProvider) { $this->couponRepository = $couponRepository; $this->dataProvider = $lineCartDataProvider; } public function get($baskedId) { $cartLines = $this->dataProvider->getAll(); $products = array(); foreach ($cartLines as $row) { $coupon = $this->couponRepository->load($row->item_id); $object = new stdClass(); $object->id = $row->item_id; $object->price = $coupon->field_price->value; $object->quantity = $row->quantity; $products[] = $object; } return BasketFactory::create($products); }
  • 114. Manel López / @mloptor #DrupalcampSpain2015 /modules/ecommerce/src/Adapters/BasketRepository.php class BasketRepository implements BasketRepositoryInterface { public function __construct($couponRepository, $lineCartDataProvider) { $this->couponRepository = $couponRepository; $this->dataProvider = $lineCartDataProvider; } public function get($baskedId) { $cartLines = $this->dataProvider->getAll(); $products = array(); foreach ($cartLines as $row) { $coupon = $this->couponRepository->load($row->item_id); $object = new stdClass(); $object->id = $row->item_id; $object->price = $coupon->field_price->value; $object->quantity = $row->quantity; $products[] = $object; } return BasketFactory::create($products); }
  • 115. Manel López / @mloptor #DrupalcampSpain2015 Controlador
  • 116. Manel López / @mloptor #DrupalcampSpain2015 class EcommerceController extends ControllerBase { public function __construct($shoppingCart, $router, $printer) { $this->shoppingCart = $shoppingCart; $this->router = $router; $this->printer = $printer; } public static function create(ContainerInterface $container) { return new static( $container->get('ecommerce.shoppingcart), $container->get('ecommerce.router'), $container->get('ecommerce.printer') ); } /* … */ } /modules/ecommerce/src/Controller/EcommerceController.php Servicio de la capa de aplicación
  • 117. Manel López / @mloptor #DrupalcampSpain2015 class EcommerceController extends ControllerBase { public function __construct($shoppingCart, $router, $printer) { $this->shoppingCart = $shoppingCart; $this->router = $router; $this->printer = $printer; } public static function create(ContainerInterface $container) { return new static( $container->get('ecommerce.shoppingcart), $container->get('ecommerce.router'), $container->get('ecommerce.printer') ); } /* … */ } /modules/ecommerce/src/Controller/EcommerceController.php Servicios Auxiliares
  • 118. Manel López / @mloptor #DrupalcampSpain2015 class EcommerceController extends ControllerBase { public function addToCart($productId) { try { $this->shoppingCart->addProductToBasket($productId, null); return $this->router->redirectToPreviosPage("Product added"); } catch (Exception $e) { return $this->router->redirectWithError($e->getMessage()); } } } /modules/ecommerce/src/Controller/EcommerceController.php
  • 119. Manel López / @mloptor #DrupalcampSpain2015 class EcommerceController extends ControllerBase { public function addToCart($productId) { try { $this->shoppingCart->addProductToBasket($productId, null); return $this->router->redirectToPreviosPage("Product added"); } catch (Exception $e) { return $this->router->redirectWithError($e->getMessage()); } } } /modules/ecommerce/src/Controller/EcommerceController.php
  • 120. Manel López / @mloptor #DrupalcampSpain2015 class EcommerceController extends ControllerBase { public function addToCart($productId) { try { $this->shoppingCart->addProductToBasket($productId, null); return $this->router->redirectToPreviosPage("Product added"); } catch (Exception $e) { return $this->router->redirectWithError($e->getMessage()); } } } /modules/ecommerce/src/Controller/EcommerceController.php
  • 121. Manel López / @mloptor #DrupalcampSpain2015 class EcommerceController extends ControllerBase { public function removeFromCart($productId) { try { $this->shoppingCart->removeProductFromBasket($productId, null); return $this->router->redirectToPreviosPage("Product removed"); } catch (Exception $e) { return $this->router->redirectWithError($e->getMessage()); } } } /modules/ecommerce/src/Controller/EcommerceController.php
  • 122. Manel López / @mloptor #DrupalcampSpain2015 class EcommerceController extends ControllerBase { public function showCart() { try { $this->printer->setDisplay('full'); return $this->printer->render(); } catch (Exception $e) { return $this->router->redirectWithError($e->getMessage()); } } } /modules/ecommerce/src/Controller/EcommerceController.php
  • 123. Manel López / @mloptor #DrupalcampSpain2015 /modules/ecommerce/ecommerce.services.yml ecommerce.printer: class: DrupalecommerceServicesPrinterService arguments: ['@ecommerce.shoppingcart']
  • 124. Manel López / @mloptor #DrupalcampSpain2015 /modules/ecommerce/ecommerce.services.yml ecommerce.printer: class: DrupalecommerceServicesPrinterService arguments: ['@ecommerce.shoppingcart']
  • 125. Manel López / @mloptor #DrupalcampSpain2015 Bloque “Shopping Cart”
  • 126. Manel López / @mloptor #DrupalcampSpain2015 /modules/ecommerce/src/Plugin/Block/ShoppingCartBlock.php /** * Provides a shooping cart block. * @Block( * id = "shoppingcartblock", * subject = @Translation("Shopping Cart block"), * admin_label = @Translation("Shopping Cart block") * ) */ class ShoppingCartBlock extends BlockBase { public function build() { $printer = Drupal::service('ecommerce.printer'); $printer->setDisplay('short'); return $printer->render(); } }
  • 127. Manel López / @mloptor #DrupalcampSpain2015 Demo
  • 128. Manel López / @mloptor #DrupalcampSpain2015 #project_wishlist Mantenible Escalable Fiable
  • 129. Manel López / @mloptor #DrupalcampSpain2015 “There are no dumb questions”
  • 130. Manel López / @mloptor #DrupalcampSpain2015 Referencias Hexagonal architecture - Alistair Cockburn http://alistair.cockburn.us/Hexagonal+architecture Desdrupalizando la lógica de negocio - Carles Climent https://vimeo.com/112570854 The Clean Architecture - Robert C. Martin http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html What is Hexagonal architecture - Philip Brown http://culttt.com/2014/12/31/hexagonal-architecture/
  • 131. Manel López / @mloptor #DrupalcampSpain2015 Referencias Hexagonal Architecture - Chris Fidao http://userscape.com/laracon/2014/chrisfidao.html http://fideloper.com/hexagonal-architecture Hexagonal Architecture - Carlos Buenosvinos http://www.slideshare.net/carlosbuenosvinos/hexagonal-architecture-php- barcelona
  • 132. Manel López / @mloptor #DrupalcampSpain2015 Gracias! … para Noa López Llombart

Editor's Notes

  1. Cuando hablamos de desacoplamr la lógica de negocio
  2. Para comprender el concepto de lógica de negocio me gustaría primer repasar algunos conceptos previos. Uno de estos primeros conceptos el el de Domino . “El dominio es el mundo del negocio”. El dominio son las ideas, conocimiento y datos del problema que intentamos resolver. El conocimiento que tenemos del dominio suele provenir de los “expertos del dominio”
  3. Los objetos del dominio son los objetos o entidades abstractas que forman el dominio. Generalmente representan a entidades lógica en el espacio del dominio del problema.
  4. Ejemplos
  5. Las reglas de negocio son aquellas que nos dicen cómo debe se comporta el dominio. Definen las relaciones , procesos y restricciones entre los objetos del dominio.
  6. El modelo del dominio es “nuestra solución al problema”. El modelo del dominio es el conocimiento estructurado relacionado a un determinado problema. El modelo del dominio puede ser código, prosa o un diagrama. Lo importante es que todos los que participen en lo puedan entender.
  7. La lógica de negocio es la parte de nuestro software que mantiene el modelo del dominio y codifica las reglas de negocio. La lógica de negocio no tiene nada que ver con las presentación de los datos y con su persistencia.
  8. En el ámbito del desarrollo web lo normal es que usemos un framework web.
  9. En ese caso, ¿Donde codificamos la lógica de negocio?
  10. Todos los frameworks ( casi todos ) actuales siguen el patron MVC. En el MVC realizamos las peticiones a los controladores, este obtiene los datos necesarios del modelo, procesa esos datos y con la vista genera un respuesta para el cliente.
  11. Por tanto, lógica de negocio se implementa en los controladores.
  12. Este patrón genera una serie de problemas: Alto nivel de acoplamiento. La codificación de las reglas se entremezcla con el acceso a los datos y la presentación de estos al usuario. Difícil testeo Al tener la lógica dentro de los controladores no podemos testearla de forma unitaria. No Single Responsability Principle SPR es uno de los principios de la programación orientada a objetos introducido por Robert C. Martin. SOLID (Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion) SPR dice que una clase debe tener solo una responsabilidad. Si se asumen mas responsabilidades será mas sensible a los cambios. Solo accesible mediante HTTP La lógica del negocio sólo es accesible desde una petición HTTP. En caso de que necesitemos acceder desde cualquier otro medio no podríamos reutilizar nuestro código. Tamaño de los controladores Nuestros controladores tienden a crecer de una manera descontrolada. y acabmos teniendo esto.,..
  13. ¿Cómo podemos hacer que nuestra lógica esté desaclopada? Es decir, ¿Que la implementación no dependa del framework?
  14. La arquitectura hexagonal es una aquitectura de software que fue introducida por Alistair CockBurn. Tambíen es conocida como arquitectura de puertos y adaptadores. Esta arquitectura promueve la separación de la diferentes responsabilidades de nuestra aplicación en diferentes capas.
  15. En el centro de la arquitectura reside la capa del Dominio. Aquí es donde implementaremos nuestro modelo del dominio. Toda la lógica de negocio debe residir aquí.
  16. Recubriendo al dominio. Situaremos la capa de aplicación. Esta capa tiene la función de hacer de API de nuestra lógica y ofrecer sus servicios al mundo exterior.
  17. Para poder conectar con nuestra lógica diferentes puertos en la capa de aplicación. La arquitectura accederá a esos puertos para obtener o enviar información al mundo exterior.
  18. Finalmente tenemos la capa de infraestructura. En esta capa es donde realizamos todos los detalles técnicos y la presentación de los datos. Aquí encontramos las implementación de los diferentes adaptadores que nos permiten conectar con la capa de la aplicación.
  19. Lo que vamoa a hacer al final es la arquitectura del Mazinger Z
  20. Como hemos visto en las anteriores transparencias. Uno de los puntos clave de esta arquitectura son los Puertos y los adaptadores.
  21. Podemos definir un puerto como una interfaz y un adaptador es una clase que implementa esa interfaz.
  22. En una arquitectura hexagonal podemos encontrar diferentes tipos de puertos.
  23. Pueden haber puertos de entrada, salida o entrada y salida.
  24. Los adaptadores son intercambiables. Podemos sustituir uno por otro sin que nuestra lógica se vea afectada.
  25. En cualquier momento podemos añadir nuevos puertos a nuestra lógica.
  26. Las fronteras entre capas son un punto importante a tener en cuenta.
  27. Para mantener la independencia de la capa es importante tener en cuenta que los servicios de una capa solo accederán a los recursos de una capa inferior. Desde la capa de infraestructura no podemos acceder a un servicio del domino o lanzar un evento, es necesario que esto sea realizado por la capa de aplicación. Esto nos asegura que podemos realizar los cambios en las capas de aplicación y dominio siempre que su interfaz no cambie.
  28. Nuestra lógica es agnóstica del mundo exterior. Podemos acceder a ella desde una aplicación web, una aplicación de consola o una proceso por lotes.
  29. Ahora es más fácil testear la lógica o al menos tiene un coste menor. Los test unitarios / integración tienen un menor costes que los test de sistema/ end to end.
  30. La lógica está aislada. Debido a esto los cambios que hacemos en una capa no afectan a las demás mientras no se toque la lógica. Esto nos permite poder hacer más refactoring y generar menos deuda técnica.
  31. Antes de revisar el código del ejemplo me gustaría explicar una serie de patrones de diseño.
  32. El primer es el patrón “Adaptar”. Como ya hemos comentado el concepto básico de la arquitectura se basa en el.
  33. Otro patrón importante es el de factoria. Las factorías nos permiten crear objetos de cierto tipo sin conocer detalles concretos de la forma en que se crean.
  34. La función principal de los repositorios es facilitarnos el acceso a la información. Dentro de esta estructura intervienen diferentes elementos. Un cliente lanza un query a un repositorio ( dame el producto con id=1, o los producto con precio < 10 € ) El respository lanza esta “query” a un proveedor de datos ( base de datos ) que nos los devuelve de una forma “estandar” un stdclass o un array. Este objeto devuelto se manda a la factoria donde convertira es objeto “sin personalidad” en un objeto concreto en este caso un objeto producto.
  35. El patrón fachada es el que implementan la mayoría del servicios del modelo del dominio. En este patrón el objeto fachada conoce el comportamiento de los distintos componenetes del sistema y los orquesta para conseguir un resultado de forma transparente a la clase cliente.
  36. Pero bueno, la arquitectura hexagonal no es el Santo Grial
  37. Como hemos visto mantener la lógica de negocio separada nos aporta escalabilidad, mantenibilidad y fiabilidad pero por otra parte añade una complejidad al proyecto. Nuestra misisón como desarrolladores es saber valorar cada proyecto concreto.
  38. El en libro Implementing Domain Driven Design podemos ver una guia. Si nuestro proyecto está muy centrado a datos y donde solo tenemos operaciones CRUD no vale la peta introducir este nivel de complejida. A medida que augmentan las historias de usuario y va apareciendo una complejidad en el negocio nos vamos acercando a la zona DDD. Hay que tener cuidado con los proyectos que pueden tener una cierta complejidad oculta. Cuando tenemos escenarios inciertos, requisitos no definmos , estros proyectos ya son subceptibles de hacer DDD. El ultimo caso es cuando no conocemos nada del dominio y debemos ir generando los models con sucesivas iteraciones. En este caso el uso de estas prácticas es muy recomendable.
  39. Cliente tiene una página de cupones de descuento y quiere incorporar un carrito de la compra. La página está desarrolada en Drupal 8 y tiene un tipo de contenido producto y una vista que muestra los últimos cupones.
  40. En el repositorio de la capa del dominio podeis ver como el diseño va emergiendo a partir de los tests. He colocado en cada commit uno paso del proceso TDD, RED, GREEN y REFACTOR.
  41. La capa del dominio está compuesta por solo dos entidades. Una cesta de la compra que contiene productos.
  42. Los archivos de la lógica están dividimos en 2 carpetas de primer nivel, una para las clases de la capa de Aplicación y otra para las clases de las capa de dominio. Dentro he creado una carpeta por cada patrón usado.
  43. La primera entidad que nos encontramos es la clase Producto. Como podemos ver esta entidad tiene una Identidad, un estado y también contiene comportamiento. Un productos está compuesto por un id, un precio unitario y la cantidad de esos productos. Una vez creado podemos incrementar la cantidad de es producto y recuperar el importe total.
  44. La primera entidad que nos encontramos es la clase Producto. Como podemos ver esta entidad tiene una Identidad, un estado y también contiene comportamiento. Un productos está compuesto por un id, un precio unitario y la cantidad de esos productos. Una vez creado podemos incrementar la cantidad de es producto y recuperar el importe total.
  45. La segunda entidad “Basket” que nos permite añadir y eliminar items así como recuperar el importe total de los productos que tenemos en la cesta.
  46. Podemos ver como el dominio encapsula las reglas de negocio. En este caso vemos cómo implementamos una reglas “Si un producto existe en el carro incrementaremos su cantidad”
  47. Podemos ver un test de integración de las dos entidades y vemos que que funcionan perfectamente.
  48. Podemo ver un test de integración de las dos entidades y vemos que que funcionan perfectamente.
  49. Podemo ver un test de integración de las dos entidades y vemos que que funcionan perfectamente.
  50. Una vez revisado las clases del modelo del dominio vamos a crear un servicio ShoppingCart de la capa de la aplicación que nos permita acceder a los recursos del modelo. No olvidemos que estos servicios son la API publica de nuestra aplicación.
  51. Lo primero que vemos es que la clase ShoppingCart se construye con 2 repositories. ProductRepository BasketREpository Estos son los dos puertos donde vamos a obtener los datos. Estos repositorios son los que deberemos implementar desde la capa de Infraestructura y pasar a la lógica.
  52. Lo primero que vemos es que la clase ShoppingCart se construye con 2 repositories. ProductRepository BasketREpository Estos son los dos puertos donde vamos a obtener los datos. Estos repositorios son los que deberemos implementar desde la capa de Infraestructura y pasar a la lógica.
  53. El resto de métdos de la clase son la API pública. Podemos ver que tenemos implementados todos los posibles casos de uso. Estos métodos hacern uso de los repositorios para obtener datos.
  54. Los servicios de la capa de aplicación implementan el patron fachada. Esto servicios saben a que clases de nuestra arquitectura deben llamar para devolver un resultado.
  55. Aquí el servicio accederá a los dos repositorios para recuperar el producto y la cesta de compra la compra. Un vez los tenga realizará las acciones adecuadas. Devolverá la cesta al repositorio para que realice la persistencia de este.
  56. Ejecutará la lógica de negocio
  57. Un procedimientos similiar siguen los otros métodos publicos.
  58. Es importante ver que estos repositorios trabajan sobre entidades del dominio.
  59. Es importante ver que estos repositorios trabajan sobre entidades del dominio
  60. Es importante ver que estos repositorios trabajan sobre entidades del dominio
  61. Por último nos queda revisar la factorias. Estas tiene las función de proveer a la capa de infraestructura de las entidades.
  62. En este caso estamos convirtiend un “objeto estandar” en un objeto “Product”
  63. Lo primero que haremos es cargar nuestra lógica de negocio. Entedremos nuestra lógica disponible a partir de el namespace malotor/shoppingcart
  64. Cuando empezamos un nuevo proyecto todos lqueremos que nuestro proyecto sea mantenible, escalable, fiable, que no tenga errores o que tenga los mínimos.