Answer the question
In order to leave comments, you need to log in
How to make friends REST API and DDD concept?
Recently, I started to get acquainted with the concept of DDD, and the question arose - how to properly handle the CREATE and UPDATE operations when the data comes through the Web API. For example, on a certain form on a web page, we fill in the fields, click "Save". A POST request with form data flies to the /api/customers address:
{
"Id": "93967a3e-384f-459a-8b50-0a0f4cc66d66",
"firstName": "Иван",
"lastName": "Череззаборногузадерищенко",
"zipCode": 245876,
"city": "Самара",
"street": "Николая Панова",
"houseNumber": 64,
"appartmentsNumber": 62,
}
public class CustomerDTO
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int ZipCode { get; set; }
public string City { get; set; }
public string Street { get; set; }
public int HouseNumber { get; set; }
public int AppartmentsNumber { get; set; }
public Guid CustomerStateId { get; set; }
}
public class Customer: Entity, IAggreagtionRoot
{
public Customer(Name name)
{
Name = name;
}
public Name Name { get; private set; }
public Address Address { get; private set; }
public CustomerState State { get; private set; }
public void ChangeState(CustomerState newState)
{
//some logic...
}
public void ChangeAddress(Address newAddress)
{
//some logic...
Address = newAddress;
}
public void ChangeName(Name newName)
{
//some logic...
Name = newName;
}
}
public class CustomerState: Entity
{
public static readonly CustomerState Regular = new CustomerState(new Guid("7beb8006-1b70-4d47-bb95-1976a2c18e9a"), "Regular");
public static readonly CustomerState VIP = new CustomerState(new Guid("c27e9e0c-a2dc-4093-80f5-e75b66997746"), "VIP");
public CustomerState(Guid id, string name)
{
Id = id;
Name = name;
}
public string Name { get; private set; }
}
public class Address: ValueType
{
public Address(int zipCode, string city, string street, int houseNumber, int appartmentsNumber)
{
ZipCode = zipCode;
City = city;
Street = street;
HouseNumber = houseNumber;
AppartmentsNumber = appartmentsNumber;
}
public int ZipCode { get; private set; }
public string City { get; private set; }
public string Street { get; private set; }
public int HouseNumber { get; private set; }
public int AppartmentsNumber { get; private set; }
}
public class Name: ValueType
{
public Name(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public string FirstName { get; private set; }
public string LastName { get; private set; }
}
public class CustomerService
{
private CustomerRepository _customerRepository;
public CustomerService(CustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
public void UpdateCustomer(CustomerDTO customerDto)
{
Guid customerId = customerDto.Id;
Customer customer = _customerRepository.GetById(customerId);
//Как мне дейстовать здесь?
customer.ChangeName(new Name(customerDto.FirstName, customerDto.LastName));
customer.ChangeAddress(new Address(customerDto.ZipCode, customerDto.City, customerDto.Street, customerDto.HouseNumber, customerDto.AppartmentsNumber));
if(customerDto.CustomerStateId == CustomerState.VIP.Id)
customer.ChangeState(CustomerState.VIP);
if (customerDto.CustomerStateId == CustomerState.Regular.Id)
customer.ChangeState(CustomerState.Regular);
//Что-то вроде этого?
//Может быть согласовать дизайн UI с дизайном доменной модели, и не позволять
//изменять статус с помощью полей формы, и изменять его отдельными действиями (например, специальными кнопками,
//которые инициируют POST-запрос www.site.ru/api/customers/2432352/PromoteToVip ?
_customerRepository.Update(customer);
}
}
{
"houseNumber": 164
}
Customer customer = _customerRepository.GetById(customerId);
var zipCode = customerDto.ZipCode == 0 ? customer.Address.ZipCode : customerDto.ZipCode;
var street = string.IsNullOrEmpty(customerDto.Street) ? customer.Address.Street : customerDto.Street;
var city = string.IsNullOrEmpty(customerDto.City) ? customer.Address.City : customerDto.City;
var appartmentsNumber = customerDto.AppartmentsNumber == 0 ? customer.Address.AppartmentsNumber : customerDto.AppartmentsNumber;
var houseNumber = customerDto.HouseNumber == 0 ? customer.Address.HouseNumber : customerDto.HouseNumber;
customer.ChangeAddress(new Address(zipCode, city, street, houseNumber, appartmentsNumber));
Answer the question
In order to leave comments, you need to log in
Greetings!
I will try to explain as briefly as possible the essence and missed points that are observed in your example.
Read about the so-called domain services ( Domain Services ) and application services ( Application Services ). In other articles, people refer to the answer to your question as Use Cases .
Use Case is a separate service that does not break stateless and has one specific task.
final class CreateCustomer
{
/**
* @var IWriteRepository Интерфейс репозитория, где на самом деле хранистя конкретная имплементация записи сущности.
*/
private $repository;
public function __construct(IWriteRepository $repositorym Validator $validator)
{
$this->repository = $repository;
...
}
/**
* @param string $id ИД будущей записи.
* @param array $data ПОТ данные которые мы получаем в момент АПи запроса.
*/
public function handle(string $id, array $data) : void
{
/**
* Валидация выбрасывает исключение если данные не валидны или возвращает массив валидных данных.
*/
$dto = $this->validator->validate($data);
$customer = new CustomerEntity(new UUID($id));
/* Ентити это сущность которое содержить исключительно только бизнес логику и безнесс поведения.
* В вашем примере у вас были разные сеттеры, в правильном подходе это лишенно смысла,
* и совсем неправильно. Думайте об методах аггрегата (Сушность с которой работает репозиторий это ни * что инное как аггрегат) как проекция бизесс логики.
* для примера в реальном мире принято говорить `Новый клиент зарегистрировался в системе`, мы никогда * не перечисляем цепь проделанных событий. Кто-то говорит `Клиент заполнил свой аддрес, ФИО, потом
* телефон, и отправил данные` ?!
*
* Если вы заметили правильно, то в конструкторе сущности передается только идентификатор.
* Такой подход похож на реальную ситуацию, что позволяет легко проектировать бизнес логику.
* регистрация клиента можно сравнить с регистрацией карточки пользователя в магазине.
* - Берём бумагу (или специальный блокнот)
* - Указываем номер клиента (обычно его как-то генерят например дата, время, или номер карточки
* которую ему выдают)
* - начинаем спрашивать клиента кго персональные данные и заполняем все в ОДНОМ процессе.
* - Потом кидаем блокнот/бумагу оратно или в колецию где лежит другая похожая инфа.
*/
$customer->register($dto['name'], ...);
/**
* Именно этот метод делает сохранение нового клиента.
* Сама реализация интереса живет в персистентном слое (`persistence layer`).
*/
$this->repository->store($customer);
}
}
final class PostController extend Controller
{
public function __construct(IWriteRepository, CreateCustomer $createCustomUseCase, RetrieveCustomer $retrieveCustomerUseCase)
{
...
}
public function __invoke(array $data)
{
$id = $this->repository->pickNextId();
$this->createCustomUseCase->handle($id, $data);
return $this->retrieveCustomerUseCase->handle($id);
}
}
the ChangeAddress method is no different from the same code in a property.
As far as I understand DDD is cool for very large and complex projects.
In a typical project - there are many business operations, all this is implemented in services that are used everywhere. One change can affect the operation of several, even unrelated business processes. Monolithic business model where everything is collected.
In a DDD project, for each business operation, almost its own project is started with its own DTOs, tables with a database and its own services. It is much easier to develop one thing without delving into and without fear of hurting other business operations.
The difficulty here is to put it all together.
So a method or property in one Entity is not really something you need to think about for a long time.
Pluralsight - Entity Framework in the Enterprise. Great example.
Question 2.
It is better to send everything, there is not much data. The very fact of the request is a significantly higher load.
But there will be problems with understanding the absence of a value from an empty field.
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question