DDD:六边形架构【洋葱】-- 领域服务

4,444 阅读13分钟

1.概述

我们将使用DDD实现Spring应用程序。 此外,我们将在六边形架构的帮助下组织层。

使用这种方法,我们可以轻松地交换应用程序的不同层。

2.六边形架构

六边形体系结构是围绕领域逻辑设计软件应用程序以将其与外部因素隔离的模型。

领域逻辑是在业务核心中指定的,我们将其称为内部部分,其余部分为外部部分。 通过端口和适配器可以从外部访问领域逻辑。

3.原则

首先,我们应该定义原则来划分我们的代码。 正如已经简要解释过的,六边形结构定义了内部和外部

相反,我们要做的是将应用程序分为三层: 应用程序(外部),域(内部)和基础结构(外部)

通过应用程序层,用户或任何其他程序与应用程序进行交互。 该区域应包含用户界面,RESTful控制器和JSON序列化库之类的内容。 它包括任何可以向我们的应用程序公开和协调域逻辑执行的内容

在领域层中,我们保留接触并实现业务逻辑的代码。 这是我们应用程序的核心。 此外,该层应与应用程序部分和基础结构部分都隔离。 最重要的是,它还应该包含定义API以便与域交互的外部部分(例如数据库)进行通信的接口。

最后,基础架构层是包含应用程序需要运行的所有内容的部分,例如数据库配置或Spring配置。 此外,它还从域层实现与基础结构相关的接口。

整体目录结构:
├─main
│ ├─java
│ │ └─com
│ │-----└─ecommerce
│ │---------└─onion
│ │--------------├─application
│ │--------------│ ├─cli
│ │--------------│ ├─request
│ │--------------│ └─response
│ │--------------├─domain
│ │--------------│ ├─repository
│ │--------------│ └─service
│ │--------------└─infrastracture
│ │------------------├─configuration
│ │------------------└─repository
│ │----------------------└─mongo
│ └─resources
└─test
├─java
│--└─com
│------└─ecommerce
│----------└─onion
│--------------└─domain
│------------------└─service
└─resources

4. Domain Layer

让我们从实现我们的核心层(即领域层)开始。 首先,我们应该创建Order类:

package com.ecommerce.onion.domain;

import java.math.BigDecimal;
import java.util.*;

/**
 * @packageName: domain(领域包)
 * @className: Order(订单领域)
 * @description: 围绕此订单领域展开业务
 * @author: luds
 * @version: v1.0
 * @date: 2021-03/25
 */
public class Order {
    /**
     * 订单ID
     */
    private UUID id;
    /**
     * 订单状态(已创建,已完成)
     */
    private OrderStatus status;
    /**
     * 订单项目列表
     */
    private List<OrderItem> orderItems;
    /**
     * 订单价格
     */
    private BigDecimal price;
    /**
     * 默认构造器
     */
    public Order(){}
    /**
     * 通过订单ID及产品构建订单
     * @param id: 订单ID
     * @param product: 产品领域实例
     */
    public Order(final UUID id, final Product product) {
        this.id = id;

        //由产品实例返回一个不变的可序列化的订单项目列表
        this.orderItems = new ArrayList<>(Collections.singletonList(new OrderItem(product)));

        //初始状态为创建
        this.status = OrderStatus.CREATED;

        //采用产品价格作为订单价格
        this.price = product.getPrice();
    }

    /**
     * 完成订单
     */
    public void complete() {
        //验证状态
        validateState();

        //把订单状态更新成:已完成
        this.status = OrderStatus.COMPLETED;
    }

    /**
     * 由产品追加新订单
     * @param product: 产品实例
     */
    public void addOrder(final Product product) {
        //验证状态
        validateState();

        //验证产品
        validateProduct(product);

        //把产品制作的订单追加到订单项目列表
        orderItems.add(new OrderItem(product));
        //追加新订单后,在订单价格基础上+产品价格
        price = price.add(product.getPrice());
    }

    /**
     * 根据ID来移除订单
     * @param id: 订单ID
     */
    public void removeOrder(final UUID id) {
        //验证状态
        validateState();
        //根据订单ID,获取订单项目
        final OrderItem orderItem = getOrderItem(id);
        //从订单项目列表移除此订单项目
        orderItems.remove(orderItem);
        //订单价格也扣除此订单项目的价格
        price = price.subtract(orderItem.getPrice());
    }

    /**
     * 根据订单ID获取订单项目
     * @param id 订单ID
     * @return 订单项目
     */
    private OrderItem getOrderItem(final UUID id) {
        //如果订单项目的产品ID过滤后,返回第一个订单项目;否则抛出异常:此ID的产品不存在。
        return orderItems.stream()
            .filter(orderItem -> orderItem.getProductId()
                .equals(id))
            .findFirst()
            .orElseThrow(() -> new DomainException("Product with " + id + " doesn't exist."));
    }

    /**
     * 验证状态
     */
    private void validateState() {
        //如果订单已经完成,就会抛出异常:这个订单已经完成
        if (OrderStatus.COMPLETED.equals(status)) {
            throw new DomainException("The order is in completed state.");
        }
    }

    /**
     * 验证产品
     * @param product: 产品实例
     */
    private void validateProduct(final Product product) {
        // 如果产品是空的,就会抛出异常:这个产品不能是空的
        if (product == null) {
            throw new DomainException("The product cannot be null.");
        }
    }

    /**
     * 获取订单ID
     * @return 返回订单ID
     */
    public UUID getId() {
        return id;
    }

    /**
     * 获取订单状态
     * @return 返回订单状态
     */
    public OrderStatus getStatus() {
        return status;
    }

    /**
     * 获取订单价格
     * @return 返回价格
     */
    public BigDecimal getPrice() {
        return price;
    }

    /**
     * 获取订单项目列表
     * @return 返回订单项目列表
     */
    public List<OrderItem> getOrderItems() {
        //变不了的列表
        return Collections.unmodifiableList(orderItems);
    }

    /**
     * 由ID、订单项目、价格、订单状态,组合成哈希值
     * @return 返回哈希值
     */
    @Override
    public int hashCode() {
        return Objects.hash(id, orderItems, price, status);
    }

    /**
     * 如果是此订单本身就返回true,如果不是订单就返回false;
     * 除了上述两种情况:如果订单ID、订单项目、价格、状态都相同,就返回true,否则false
     * @param obj: 比较对象
     * @return 比较结果
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!(obj instanceof Order))
            return false;
        Order other = (Order) obj;
        return Objects.equals(id, other.id) && Objects.equals(orderItems, other.orderItems) && Objects.equals(price, other.price) && status == other.status;
    }
}


这是我们的聚合根。 与我们的业务逻辑有关的所有内容都将实践一下。 另外,Order负责将自己保持在正确的状态:

  • 只能使用给定的ID并基于一个产品创建订单-构造函数本身也以CREATED状态创建订单
  • 订单完成后,便无法更改OrderItems
  • 像设置器一样,不可能从领域对象外部更改Order。

此外,Order类还负责创建其OrderItem。 然后创建OrderItem类:

package com.ecommerce.onion.domain;

import java.math.BigDecimal;
import java.util.Objects;
import java.util.UUID;
/**
 * @packageName: domain(领域包)
 * @className: OrderItem(订单项目领域)
 * @description: 订单项目列表中使用的订单项目
 * @author: luds
 * @version: v1.0
 * @date: 2021-03/25
 */
public class OrderItem {
    /**
     * 订单项目ID
     */
    private UUID productId;
    /**
     * 订单项目价格
     */
    private BigDecimal price;

    /**
     * 默认构造器
     */
    public OrderItem(){}
    /**
     * 由产品构建订单项目
     * @param product 产品实例
     */
    public OrderItem(final Product product) {
        // 产品ID取出来,给订单项目ID
        this.productId = product.getId();
        // 产品价格取出来,给订单项目价格
        this.price = product.getPrice();
    }

    /**
     * 获取订单项目ID
     * @return 返回订单项目ID
     */
    public UUID getProductId() {
        return productId;
    }

    /**
     * 获取订单项目价格
     * @return 返回订单项目u价格
     */
    public BigDecimal getPrice() {
        return price;
    }
    /**
     * 如果是此订单项目本身就返回true,如果不是订单项目就返回false;
     * 除了上述两种情况:如果订单项目ID、价格都相同,就返回true,否则false
     * @param o: 比较对象
     * @return 比较结果
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        OrderItem orderItem = (OrderItem) o;
        return Objects.equals(productId, orderItem.productId) && Objects.equals(price, orderItem.price);
    }
    /**
     * 由ID、订单项目价格组合成哈希值
     * @return 返回哈希值
     */
    @Override
    public int hashCode() {
        return Objects.hash(productId, price);
    }
}

如我们所见,OrderItem是基于产品创建的。 它保留对它的引用并存储产品的当前价格。

<订单领域结构图(PlantUML)>

接下来,我们将创建一个存储库接口(Hexagonal Architecture中的端口)。 该接口的实现将在基础设施层中:

package com.ecommerce.onion.domain.repository;

import com.ecommerce.onion.domain.Order;

import java.util.Optional;
import java.util.UUID;
/**
 * @packageName: domain(领域.资料库)
 * @className: Order(订单资料库)
 * @description: 围绕订单资料库的接口定义
 * @author: luds
 * @version: v1.0
 * @date: 2021-03/25
 */
public interface OrderRepository {
    /**
     * 由ID寻找订单
     * @param id 订单ID
     * @return 返回订单
     */
    Optional<Order> findById(UUID id);

    /**
     * 持久化订单
     * @param order 订单实例
     */
    void save(Order order);
}

最后,我们应确保在每次操作后始终保存订单。 为此,我们将定义一个领域服务,该领域服务通常包含不能成为根目录一部分的逻辑

package com.ecommerce.onion.domain.service;

import com.ecommerce.onion.domain.Order;
import com.ecommerce.onion.domain.Product;
import com.ecommerce.onion.domain.repository.OrderRepository;

import java.util.UUID;
/**
 * @packageName: domain(领域.服务包)
 * @className: DomainOrderService(领域订单服务)
 * @description: 实现订单服务
 * @author: luds
 * @version: v1.0
 * @date: 2021-03/25
 */
public class DomainOrderService implements OrderService {

    /**
     * 订单资料库
     */
    private final OrderRepository orderRepository;

    /**
     * 由订单资料库构建此领域订单服务
     * @param orderRepository 订单资料库
     */
    public DomainOrderService(final OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    /**
     * 由此产品创建订单,并返回订单ID
     * @param product 产品实例
     * @return 返回订单ID
     */
    @Override
    public UUID createOrder(final Product product) {
        //由UUID.randomUUID()、产品创建订单
        final Order order = new Order(UUID.randomUUID(), product);
        //调用资料库把创建的订单进行持久化
        orderRepository.save(order);
        //从订单返回ID
        return order.getId();
    }
    /**
     * 往此订单ID上追加产品
     * @param id 订单ID
     * @param product 产品实例
     */
    @Override
    public void addProduct(final UUID id, final Product product) {
        //由ID获取订单
        final Order order = getOrder(id);
        //往订单上追加产品
        order.addOrder(product);
        //调用资料库把加好的订单,并进行持久化
        orderRepository.save(order);
    }
    /**
     * 完成此ID的订单
     * @param id 订单ID
     */
    @Override
    public void completeOrder(final UUID id) {
        //由ID获取订单
        final Order order = getOrder(id);
        //并完成此订单
        order.complete();
        //调用资料库把完成的订单,进行持久化
        orderRepository.save(order);
    }
    /**
     * 从订单里删除此ID的产品
     * @param id 订单ID
     * @param productId 产品ID
     */
    @Override
    public void deleteProduct(final UUID id, final UUID productId) {
        //由ID获取订单
        final Order order = getOrder(id);
        //并从订单上移除为ID的产品
        order.removeOrder(productId);
        //调用资料库把移除的订单,进行持久化
        orderRepository.save(order);
    }

    /**
     * 由此ID获取订单
     * @param id 订单ID
     * @return 返回订单
     */
    private Order getOrder(UUID id) {
        //调用资料库,由ID查找;如果找到返回订单,否则抛出异常:此ID的订单不存在
        return orderRepository
          .findById(id)
          .orElseThrow(() -> new RuntimeException("Order with given id doesn't exist"));
    }
}

<领域订单服务的结构图(PlantUML)>

<领域订单服务的调用关系图(PlantUML)>

  • createOrder

  • addProduct

  • completeOrder

  • deleteProduct

image.png

在六边形体系结构中,此服务是实现端口的适配器。 另外,我们不会将其注册为Spring Bean,因为从领域的角度来看,这在内部,而Spring配置在外部。 稍后,我们将在基础架构层中将其与Spring手动连接。

因为领域层与应用程序层和基础结构层完全分离,所以我们也可以独立地对其进行测试:

package com.ecommerce.onion.domain.service;

import com.ecommerce.onion.domain.Order;
import com.ecommerce.onion.domain.OrderProvider;
import com.ecommerce.onion.domain.Product;
import com.ecommerce.onion.domain.repository.OrderRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;

import java.math.BigDecimal;
import java.util.Optional;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;
import static org.mockito.internal.verification.VerificationModeFactory.times;

/**
 * @packageName: domain(领域.服务包)
 * @className: DomainOrderServiceUnitTest(领域订单服务单体测试)
 * @description: 实现订单服务的单体测试
 * @author: luds
 * @version: v1.0
 * @date: 2021-03/25
 */
public class DomainOrderServiceUnitTest {
    /**
     * 订单资料库
     */
    private OrderRepository orderRepository;
    /**
     * 测试对象:领域订单服务
     */
    private DomainOrderService tested;

    /**
     * 在每个测试用例前,初始化
     */
    @BeforeEach
    void setUp() {
        //模仿化订单资料库
        orderRepository = mock(OrderRepository.class);
        //用订单资料库,实例化领域服务
        tested = new DomainOrderService(orderRepository);
    }
    /**
     * 用例:创建订单并持久化
     */
    @Test
    void shouldCreateOrder_thenSaveIt() {
        //定义一个产品
        final Product product = new Product(UUID.randomUUID(), BigDecimal.TEN, "productName");

        final UUID id = tested.createOrder(product);

        verify(orderRepository).save(any(Order.class));
        assertNotNull(id);
    }

    /**
     * 用例:追加产品并持久化
     */
    @Test
    void shouldAddProduct_thenSaveOrder() {
        final Order order = spy(OrderProvider.getCreatedOrder());
        final Product product = new Product(UUID.randomUUID(), BigDecimal.TEN, "test");
        when(orderRepository.findById(order.getId())).thenReturn(Optional.of(order));

        tested.addProduct(order.getId(), product);

        verify(orderRepository).save(order);
        verify(order).addOrder(product);
    }
    /**
     * 用例:追加产品并抛异常
     */
    @Test
    void shouldAddProduct_thenThrowException() {
        final Product product = new Product(UUID.randomUUID(), BigDecimal.TEN, "test");
        final UUID id = UUID.randomUUID();
        when(orderRepository.findById(id)).thenReturn(Optional.empty());

        final Executable executable = () -> tested.addProduct(id, product);

        verify(orderRepository, times(0)).save(any(Order.class));
        assertThrows(RuntimeException.class, executable);
    }

    /**
     * 用例:完成订单并持久化
     */
    @Test
    void shouldCompleteOrder_thenSaveIt() {
        final Order order = spy(OrderProvider.getCreatedOrder());
        when(orderRepository.findById(order.getId())).thenReturn(Optional.of(order));

        tested.completeOrder(order.getId());

        verify(orderRepository).save(any(Order.class));
        verify(order).complete();
    }

    /**
     * 从订单删除产品并持久化
     */
    @Test
    void shouldDeleteProduct_thenSaveOrder() {
        final Order order = spy(OrderProvider.getCreatedOrder());
        final UUID productId = order
          .getOrderItems()
          .get(0)
          .getProductId();

        when(orderRepository.findById(order.getId())).thenReturn(Optional.of(order));

        tested.deleteProduct(order.getId(), productId);

        verify(orderRepository).save(order);
        verify(order).removeOrder(productId);
    }
}

5.应用层

这里,我们将实现应用程序层。 我们将允许用户通过RESTful API与我们的应用程序进行通信。

因此,让我们创建OrderController:

package com.ecommerce.onion.application;

import com.ecommerce.onion.application.request.AddProductRequest;
import com.ecommerce.onion.application.request.CreateOrderRequest;
import com.ecommerce.onion.application.response.OrderResponse;
import com.ecommerce.onion.domain.Product;
import com.ecommerce.onion.domain.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import java.util.UUID;
/**
 * @packageName: application(应用包)
 * @className: OrderController(订单控制器)
 * @description: 围绕此订单领域展开业务
 * @author: luds
 * @version: v1.0
 * @date: 2021-04/03
 */
@RestController
@RequestMapping("/orders")
public class OrderController {
	/**
	 * 订单服务
	 */
	private final OrderService orderService;


	/**
	 * 由订单服务自动装配订单控制器
	 * @param orderService
	 */
	@Autowired
	public OrderController(OrderService orderService) {
		this.orderService = orderService;
	}

	/**
	 * 由创建订单请求创建订单,并返回创建订单响应
	 * @param createOrderRequest 创建订单请求
	 * @return 返回创建订单响应
	 */
	@PostMapping(produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
	OrderResponse createOrder(@RequestBody final CreateOrderRequest createOrderRequest) {

		Product product = createOrderRequest.getProduct();

		final UUID id = orderService.createOrder(product);

		return new OrderResponse(id,product.getId());
	}

	/**
	 * 此订单ID上追加产品请求
	 * @param id 订单ID
	 * @param addProductRequest 追加产品请求
	 */
	@PostMapping(value = "/{id}/products", consumes = MediaType.APPLICATION_JSON_VALUE)
	OrderResponse addProduct(@PathVariable final UUID id, @RequestBody final AddProductRequest addProductRequest) {
		Product product = addProductRequest.getProduct();

		orderService.addProduct(id, product);

		return new OrderResponse(id,product.getId());
	}

	/**
	 * 从此订单ID上删除产品ID
	 * @param id 订单ID
	 * @param productId 产品ID
	 */
	@DeleteMapping(value = "/{id}/products", consumes = MediaType.APPLICATION_JSON_VALUE)
	void deleteProduct(@PathVariable final UUID id, @RequestParam final UUID productId) {
		orderService.deleteProduct(id, productId);
	}

	/**
	 * 完成订单
	 * @param id 订单ID
	 */
	@PostMapping("/{id}/complete")
	void completeOrder(@PathVariable final UUID id) {
		orderService.completeOrder(id);
	}
}

<订单控制器的结构图(PlantUML)>

image.png

这个简单的Spring Rest控制器负责协调域逻辑的执行。 该控制器使外部RESTful接口适应我们的域。 它是通过从OrderService(端口)调用适当的方法来完成的。

6.基础设施层

基础结构层包含运行应用程序所需的逻辑。 因此,我们将从创建配置类开始。 首先,让我们实现一个将OrderService注册为Spring bean的类:

package com.ecommerce.onion.infrastracture.configuration;

import com.ecommerce.onion.DomainLayerApplication;
import com.ecommerce.onion.domain.repository.OrderRepository;
import com.ecommerce.onion.domain.service.DomainOrderService;
import com.ecommerce.onion.domain.service.OrderService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
 * @packageName: infrastracture.configuration(基础设施.配置)
 * @className: BeanConfiguration(bean配置)
 * @description: 服务配置
 * @author: luds
 * @version: v1.0
 * @date: 2021-03/25
 */
@Configuration
@ComponentScan(basePackageClasses = DomainLayerApplication.class)
public class BeanConfiguration {

	/**
	 * 由订单资料库装配订单服务
	 * @param orderRepository 订单资料库
	 * @return 返回订单服务
	 */
	@Bean
	OrderService orderService(final OrderRepository orderRepository) {
		return new DomainOrderService(orderRepository);
	}
}

接下来,让我们创建负责启用我们将使用的Spring Data存储库的配置:

@EnableMongoRepositories(basePackageClasses = SpringDataMongoOrderRepository.class)
public class MongoDBConfiguration {
}

我们使用了basePackageClasses属性,因为这些存储库只能位于基础结构层中。 因此,Spring没有理由扫描整个应用程序。 此外,此类可以包含与在MongoDB和我们的应用程序之间建立连接有关的所有内容。 最后,我们将从领域层实现OrderRepository。 我们将在实现中使用SpringDataMongoOrderRepository:

package com.ecommerce.onion.infrastracture.repository.mongo;

import com.ecommerce.onion.domain.Order;
import com.ecommerce.onion.domain.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import java.util.Optional;
import java.util.UUID;
/**
 * @packageName: infrastracture.repository.mongo(基础设施.资料库卡.mongo)
 * @className: MongoDbOrderRepository(MongoDb订单资料库)
 * @description: MongoDB存储方式
 * @author: luds
 * @version: v1.0
 * @date: 2021-03/26
 */
@Component
@Primary
public class MongoDbOrderRepository implements OrderRepository {

    /**
     * 使用SpringData实现的MongoDb订单资料库
     */
    private final SpringDataMongoOrderRepository orderRepository;

    /**
     * SpringData实现的MongoDb资料库来构建MongoDb订单资料库
     * @param orderRepository 订单资料库
     */
    @Autowired
    public MongoDbOrderRepository(final SpringDataMongoOrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    /**
     * 根据ID查找订单
     * @param id 订单ID
     * @return 返回订单实例
     */
    @Override
    public Optional<Order> findById(final UUID id) {
        return orderRepository.findById(id);
    }

    /**
     * 持久化订单
     * @param order 订单实例
     */
    @Override
    public void save(final Order order) {
        orderRepository.save(order);
    }
}

此实现将我们的订单存储在MongoDB中。 在六边形体系结构中,此实现也是适配器。 MongoDB安装请参考:juejin.cn/post/694427…

7.好处

这种方法的第一个优点是我们将每一层的工作分开。 我们可以专注于一层而不影响其他层。 此外,它们自然更容易理解,因为它们每个都专注于其逻辑。

另一个很大的优势是,我们已经将领域逻辑与其他所有事物隔离开来。 领域部分仅包含业务逻辑,可以轻松移至其他环境。 实际上,让我们更改基础结构层以将Cassandra用作数据库:

package com.ecommerce.onion.infrastracture.repository.cassandra;

import com.ecommerce.onion.domain.Order;
import com.ecommerce.onion.domain.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Optional;
import java.util.UUID;
/**
 * @packageName: infrastracture.repository.cassandra(基础设施.资料库卡.cassandra)
 * @className: CassandraDbOrderRepository(CassandraDB订单资料库)
 * @description: CassandraDB存储方式
 * @author: luds
 * @version: v1.0
 * @date: 2021-03/26
 */
@Component
public class CassandraDbOrderRepository implements OrderRepository {
    /**
     * 使用SpringData实现的CassandraDB订单资料库
     */
    private final SpringDataCassandraOrderRepository orderRepository;

    /**
     * SpringData实现的CassandraDB资料库来构建CassandraDB订单资料库
     * @param orderRepository 订单资料库
     */
    @Autowired
    public CassandraDbOrderRepository(SpringDataCassandraOrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
    /**
     * 根据ID查找订单
     * @param id 订单ID
     * @return 返回订单实例
     */
    @Override
    public Optional<Order> findById(UUID id) {
        Optional<OrderEntity> orderEntity = orderRepository.findById(id);
        if (orderEntity.isPresent()) {
            return Optional.of(orderEntity.get()
                .toOrder());
        } else {
            return Optional.empty();
        }
    }
    /**
     * 持久化订单
     * @param order 订单实例
     */
    @Override
    public void save(Order order) {
        orderRepository.save(new OrderEntity(order));
    }

}

与MongoDB不同,我们现在使用OrderEntity将领域保留在数据库中。

如果我们在Order域对象中添加特定于技术的注释,则将违反基础结构层和领域层之间的解耦关系。 存储库使领域适应我们的持久化需求。

让我们更进一步,将RESTful应用程序转换为命令行应用程序:

package com.ecommerce.onion.application.cli;

import com.ecommerce.onion.domain.Product;
import com.ecommerce.onion.domain.service.OrderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.UUID;
/**
 * @packageName: application.cli(应用.客户端包)
 * @className: CliOrderController(客户端订单控制器)
 * @description: 围绕此订单领域展开业务
 * @author: luds
 * @version: v1.0
 * @date: 2021-03/25
 */
@Component
public class CliOrderController {

    private static final Logger LOG = LoggerFactory.getLogger(CliOrderController.class);
    /**
     * 订单服务
     */
    private final OrderService orderService;
    /**
     * 由订单服务自动装配订单控制器
     * @param orderService
     */
    @Autowired
    public CliOrderController(OrderService orderService) {
        this.orderService = orderService;
    }
    /**
     * 创建完成订单
     */
    public void createCompleteOrder() {
        LOG.info("<<Create complete order>>");
        UUID orderId = createOrder();
        orderService.completeOrder(orderId);
    }

    /**
     * 创建不完整的订单
     */
    public void createIncompleteOrder() {
        LOG.info("<<Create incomplete order>>");
        UUID orderId = createOrder();
    }

    /**
     * 创建订单
     * @return 返回订单ID
     */
    private UUID createOrder() {
        LOG.info("Placing a new order with two products");
        Product mobilePhone = new Product(UUID.randomUUID(), BigDecimal.valueOf(200), "mobile");
        Product razor = new Product(UUID.randomUUID(), BigDecimal.valueOf(50), "razor");
        LOG.info("Creating order with mobile phone");
        UUID orderId = orderService.createOrder(mobilePhone);
        LOG.info("Adding a razor to the order");
        orderService.addProduct(orderId, razor);
        return orderId;
    }
}

与以前不同,我们现在已经硬连接了一组与我们的领域交互的预定义操作。 例如,我们可以使用它来用模拟数据填充我们的应用程序。

即使我们完全改变了应用程序的用途,我们也没有触及领域层。

8.结论

在本文中,我们学习了如何将与我们的应用程序相关的逻辑分成特定的层。

首先,我们定义了三个主要层:应用程序,领域和基础结构。 之后,我们描述了如何填充它们并解释了优点。

然后,我们提出了每一层的实现:

最后,我们交换了应用程序和基础架构层,而不会影响领域。

完整代码:gitee.com/actual-comb…

参考:www.baeldung.com/hexagonal-a… 延伸阅读:resourceful-badger-85476-dev-ed.my.salesforce.com/home/home.j…