Принципы построения доменной модели
Одна из основных причин разделения сервиса на два слоя:
- Абстрактая часть сервиса;
- Конфигурация сервиса под конкретную информационную систему;
это дать возможность дорабатывать доменную модель под конкретного заказчика.
Рассмотрим принципы построения доменной модели сервиса на примере.
Есть две информационные системы для разных заказчиков customer1 и customer2. Цель - разработать сервис контактов для эти двух информационных систем. Одним из требований является то, что информация об организациях в этих информационных системах имела бы разную структуру.
Для каждого из заказчиков создается два приложения, каждое из которых является контейнером для сервиса контактов. Упрощенная структура модулей:
- Приложение-контейнер для сервиса контактов заказчика customer1:
project
vendor
nnx-contract
contract
contract-core
customer1-contract
organization
- Приложение-контейнер для сервиса контактов заказчика customer2:
project
vendor
nnx-contract
contract
contract-core
customer2-contract
organization
Т.е реализация сущностей, подстраивающих доменную модель под конкретного заказчика, выносится в соответствующие модули customer1-contract\organization и customer2-contract\organization.
Доменная модель в Core модулях.
У каждого сервиса есть Core-модуль, в котором сосредоточено описание базовых сущностей сервиса, а также предоставляются сервисы для работы с наиболее общим функционалом.
Спецификой Core-модулей, является то, что в сервисах, расположенных в данных модулях, неизвестно, с каким именно классом сущности будет происходить работа.
В случае модуля nnx-contract/contract-core заранее неизвестно имя класса организации. Так как сущность организации, которая будет использоваться в сервисе, может быть реализована в другом модуле (в приведенном примере это customer1-contract\organization и customer2-contract\organization).
Для достижения такой гибкости используется следующий подход:
- Для каждой сущности, которая может расширяться в других модулях, заводится интерфейс;
- В рамках сервиса модуля работа осуществляется с объектами, имлементирующими заданный интерфейс, т.е. не допускается использовать имена классов;
- Если есть часть свойств, которые гарантированно будут у всех классов, имплементирующих данный интерфейс, то создается абстрактный класс;
- Создается класс сущности, являющейся родительской, для сущностей, располагаемых в других модулях.
Расмотрим конкретный пример:
Создание сущности контракта:
namespace Nnx\Contract\Core\Entity;
//Интерфейс сущности контракта. В сервисах работает с интерфейсом, а не с реализацией.
interface ContractInterface
{
public function getId();
public function getRegisterNumber();
}
namespace Nnx\Contract\Core\Entity;
use Doctrine\ORM\Mapping as ORM;
//Выносим в абстрактную часть, свойства, которые гарантированно есть у всех классов контрактов
/**
* Class AbstractContract
*
* @ORM\MappedSuperclass()
*/
abstract class AbstractContract implements ContractInterface
{
/**
* @var int
*
* @ORM\Id()
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* Реестровый номер
*
* @var string
*
* @ORM\Column(name="register_number", type="string", nullable=true)
*/
protected $registerNumber;
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @return string
*/
public function getRegisterNumber()
{
return $this->registerNumber;
}
}
namespace Nnx\Contract\Core\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Непосредственная реализация сущности контракта
*
* @ORM\Entity
* @ORM\Table(name="contract")
*/
class Contract extends AbstractContract
{
/**
* Заказчик
*
* @var Customer
*
* @ORM\ManyToOne(targetEntity="DefaultCustomerInfo", fetch="LAZY",
* cascade={"persist", "remove"})
* @ORM\JoinColumn(name="customer_id", referencedColumnName="id")
*/
protected $customer;
/**
* Поставщики
*
* @var Supplier
*
* @ORM\ManyToMany(targetEntity="Supplier", cascade={"persist", "remove"})
* @ORM\JoinTable(name="contract_supplier",
* joinColumns={@ORM\JoinColumn(name="contract_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="supplier_id", referencedColumnName="id")}
* )
*/
protected $suppliers;
/**
* @return Customer
*/
public function getCustomer()
{
return $this->customer;
}
/**
* @return ArrayCollection|Supplier[]
*/
public function getSuppliers()
{
return $this->suppliers;
}
}
Создаем сущности заказчика и поставщика:
namespace Nnx\Contract\Core\Entity;
/**
* Interface OrganizationInterface
*
*/
interface OrganizationInterface
{
/**
* @return int
*/
public function getId();
/**
* @return string
*/
public function getInn();
}
Абстрактная реализация организации:
namespace Nnx\Contract\Core\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="contract_organization")
* @ORM\InheritanceType(value="SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="type", type="string")
*/
abstract class AbstractOrganization implements OrganizationInterface
{
/**
* @var int
*
* @ORM\Id()
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* ИНН
* @var string
*
* @ORM\Column(name="inn", type="string", nullable=true)
*/
protected $inn;
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @return string
*/
public function getInn()
{
return $this->inn;
}
}
Заказчик:
namespace Nnx\Contract\Core\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
*/
class Customer extends AbstractOrganization
{
}
Поставщик:
namespace Nnx\Contract\Core\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
*/
class Supplier extends AbstractOrganization
{
}
Расширение доменной модели под конкретного заказчика
В предыдущем пункте был приведен пример, описывающий сущности Contract, а также сущности Customer и Supplier. Поскольку структура сущностей Customer и Supplier может меняться от одной информационной системы к другой, то описание этих сущностей выносится в ту часть сервиса, которая расширяет абстрактную часть.
Пусть у нас есь сервис контрактов со следующей структурой каталогов:
project
vendor
nnx-contract
contract
contract-core
customer1-contract
organization
В nnx-contract/contract-core описаны сущность Contract, а также Customer и Supplier.
Расмотрим на примере расширение этих сущностей в модуле:
customer1-contract/organization
Заказчик:
namespace Customer1\Contract\Core\Entity;
use Doctrine\ORM\Mapping as ORM;
use Nnx\Contract\Core\Entity\Customer;
/**
* @ORM\Entity()
*/
class ExtendedCustomer extends Customer
{
/**
* @var string
*
* @ORM\Column(name="custom_field1", type="string", nullable=true)
*/
protected $customField1;
}
Поставщик:
namespace Customer1\Contract\Core\Entity;
use Doctrine\ORM\Mapping as ORM;
use Nnx\Contract\Core\Entity\Supplier;
/**
* @ORM\Entity()
*/
class ExtendedSupplier extends Supplier
{
/**
* @var string
*
* @ORM\Column(name="custom_field2", type="string", nullable=true)
*/
protected $customField2;
}