Recently, I began to get acquainted with the concept of DDD, and the question arose of how to act correctly in CREATE and UPDATE operations when data comes through the Web API. For example, on some form on a web page, we fill in the fields, click "Save." A POST request is sent to the address / api / customers with the form data:
{ "Id": "93967a3e-384f-459a-8b50-0a0f4cc66d66", "firstName": "Иван", "lastName": "Череззаборногузадерищенко", "zipCode": 245876, "city": "Самара", "street": "Николая Панова", "houseNumber": 64, "appartmentsNumber": 62, } On the Web API side, it is deserialized into a DTO class:
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; } } We have the following domain model (done very roughly to demonstrate an example):
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; } } Question 1. How to change the Customer object from the DTO object? The following comes to mind:
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); } } Question 2. What should I do if I want to send only the changes, and not the entire object? For example, only the house number has changed, and in order to minimize the data being sent, I want to send such a request:
{ "houseNumber": 164 } How in this case should I change the Value-type Address if its constructor requires all the parameters? Maybe something like this ?:
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)); Question 3. What if an object has many properties, say 40. For example, 20 of them do not affect the consistency of the model (ie, purely informational, and you do not need to monitor their change). To do for each method of setting values (which I feel sad), or just open them for editing (to make the setter public) to the external code?
PS I think that the public void ChangeState (CustomerState newState) method should be removed according to the canons of DDD, and instead introduce methods like PromoteToVip () and DowngradeToRegular (), but let it be as it is in this example.