SpringBoot實(shí)現(xiàn)六邊形架構(gòu)的三種不同方式詳解
一、六邊形架構(gòu)基本原理
1.1 核心概念
六邊形架構(gòu)由Alistair Cockburn于2005年提出,其核心思想是將應(yīng)用程序的內(nèi)部業(yè)務(wù)邏輯與外部交互隔離開來(lái)。這種架構(gòu)主要由三部分組成:
- 領(lǐng)域(Domain) :包含業(yè)務(wù)邏輯和領(lǐng)域模型,是應(yīng)用程序的核心
- 端口(Ports) :定義應(yīng)用程序與外部世界交互的接口
- 適配器(Adapters) :實(shí)現(xiàn)端口接口,連接外部世界與應(yīng)用程序
1.2 端口分類
端口通常分為兩類:
- 輸入端口(Primary/Driving Ports) :允許外部系統(tǒng)驅(qū)動(dòng)應(yīng)用程序,如REST API、命令行接口
- 輸出端口(Secondary/Driven Ports) :允許應(yīng)用程序驅(qū)動(dòng)外部系統(tǒng),如數(shù)據(jù)庫(kù)、消息隊(duì)列、第三方服務(wù)
1.3 六邊形架構(gòu)的優(yōu)勢(shì)
- 業(yè)務(wù)邏輯獨(dú)立性:核心業(yè)務(wù)邏輯不依賴于特定技術(shù)框架
- 可測(cè)試性:業(yè)務(wù)邏輯可以在沒有外部依賴的情況下進(jìn)行測(cè)試
- 靈活性:可以輕松替換技術(shù)實(shí)現(xiàn)而不影響業(yè)務(wù)邏輯
- 關(guān)注點(diǎn)分離:明確區(qū)分了業(yè)務(wù)規(guī)則和技術(shù)細(xì)節(jié)
- 可維護(hù)性:使代碼結(jié)構(gòu)更加清晰,便于維護(hù)和擴(kuò)展
二、經(jīng)典六邊形架構(gòu)實(shí)現(xiàn)
2.1 項(xiàng)目結(jié)構(gòu)
經(jīng)典六邊形架構(gòu)嚴(yán)格遵循原始設(shè)計(jì)理念,通過明確的包結(jié)構(gòu)分離領(lǐng)域邏輯和適配器:
src/main/java/com/example/demo/
├── domain/ # 領(lǐng)域?qū)?br />│ ├── model/ # 領(lǐng)域模型
│ ├── service/ # 領(lǐng)域服務(wù)
│ └── port/ # 端口定義
│ ├── incoming/ # 輸入端口
│ └── outgoing/ # 輸出端口
├── adapter/ # 適配器層
│ ├── incoming/ # 輸入適配器
│ │ ├── rest/ # REST API適配器
│ │ └── scheduler/ # 定時(shí)任務(wù)適配器
│ └── outgoing/ # 輸出適配器
│ ├── persistence/ # 持久化適配器
│ └── messaging/ # 消息適配器
└── application/ # 應(yīng)用配置
└── config/ # Spring配置類
2.2 代碼實(shí)現(xiàn)
2.2.1 領(lǐng)域模型
// 領(lǐng)域模型 package com.example.demo.domain.model; public class Product { private Long id; private String name; private double price; private int stock; // 構(gòu)造函數(shù)、getter和setter // 領(lǐng)域行為 public boolean isAvailable() { return stock > 0; } public void decreaseStock(int quantity) { if (quantity > stock) { throw new IllegalArgumentException("Not enough stock"); } this.stock -= quantity; } }
2.2.2 輸入端口
// 輸入端口(用例接口) package com.example.demo.domain.port.incoming; import com.example.demo.domain.model.Product; import java.util.List; import java.util.Optional; public interface ProductService { List<Product> getAllProducts(); Optional<Product> getProductById(Long id); Product createProduct(Product product); void updateStock(Long productId, int quantity); }
2.2.3 輸出端口
// 輸出端口(存儲(chǔ)庫(kù)接口) package com.example.demo.domain.port.outgoing; import com.example.demo.domain.model.Product; import java.util.List; import java.util.Optional; public interface ProductRepository { List<Product> findAll(); Optional<Product> findById(Long id); Product save(Product product); void deleteById(Long id); }
2.2.4 領(lǐng)域服務(wù)實(shí)現(xiàn)
// 領(lǐng)域服務(wù)實(shí)現(xiàn) package com.example.demo.domain.service; import com.example.demo.domain.model.Product; import com.example.demo.domain.port.incoming.ProductService; import com.example.demo.domain.port.outgoing.ProductRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; @Service public class ProductServiceImpl implements ProductService { private final ProductRepository productRepository; public ProductServiceImpl(ProductRepository productRepository) { this.productRepository = productRepository; } @Override public List<Product> getAllProducts() { return productRepository.findAll(); } @Override public Optional<Product> getProductById(Long id) { return productRepository.findById(id); } @Override public Product createProduct(Product product) { return productRepository.save(product); } @Override @Transactional public void updateStock(Long productId, int quantity) { Product product = productRepository.findById(productId) .orElseThrow(() -> new RuntimeException("Product not found")); product.decreaseStock(quantity); productRepository.save(product); } }
2.2.5 輸入適配器(REST API)
// REST適配器 package com.example.demo.adapter.incoming.rest; import com.example.demo.domain.model.Product; import com.example.demo.domain.port.incoming.ProductService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/products") public class ProductController { private final ProductService productService; public ProductController(ProductService productService) { this.productService = productService; } @GetMapping public List<Product> getAllProducts() { return productService.getAllProducts(); } @GetMapping("/{id}") public ResponseEntity<Product> getProductById(@PathVariable Long id) { return productService.getProductById(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @PostMapping public Product createProduct(@RequestBody Product product) { return productService.createProduct(product); } @PutMapping("/{id}/stock") public ResponseEntity<Void> updateStock( @PathVariable Long id, @RequestParam int quantity) { productService.updateStock(id, quantity); return ResponseEntity.ok().build(); } }
2.2.6 輸出適配器(持久化)
// JPA實(shí)體 package com.example.demo.adapter.outgoing.persistence.entity; import javax.persistence.*; @Entity @Table(name = "products") public class ProductEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private double price; private int stock; // 構(gòu)造函數(shù)、getter和setter } // JPA倉(cāng)庫(kù) package com.example.demo.adapter.outgoing.persistence.repository; import com.example.demo.adapter.outgoing.persistence.entity.ProductEntity; import org.springframework.data.jpa.repository.JpaRepository; public interface JpaProductRepository extends JpaRepository<ProductEntity, Long> { } // 持久化適配器 package com.example.demo.adapter.outgoing.persistence; import com.example.demo.adapter.outgoing.persistence.entity.ProductEntity; import com.example.demo.adapter.outgoing.persistence.repository.JpaProductRepository; import com.example.demo.domain.model.Product; import com.example.demo.domain.port.outgoing.ProductRepository; import org.springframework.stereotype.Component; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @Component public class ProductRepositoryAdapter implements ProductRepository { private final JpaProductRepository jpaRepository; public ProductRepositoryAdapter(JpaProductRepository jpaRepository) { this.jpaRepository = jpaRepository; } @Override public List<Product> findAll() { return jpaRepository.findAll().stream() .map(this::mapToDomain) .collect(Collectors.toList()); } @Override public Optional<Product> findById(Long id) { return jpaRepository.findById(id) .map(this::mapToDomain); } @Override public Product save(Product product) { ProductEntity entity = mapToEntity(product); ProductEntity savedEntity = jpaRepository.save(entity); return mapToDomain(savedEntity); } @Override public void deleteById(Long id) { jpaRepository.deleteById(id); } private Product mapToDomain(ProductEntity entity) { Product product = new Product(); product.setId(entity.getId()); product.setName(entity.getName()); product.setPrice(entity.getPrice()); product.setStock(entity.getStock()); return product; } private ProductEntity mapToEntity(Product product) { ProductEntity entity = new ProductEntity(); entity.setId(product.getId()); entity.setName(product.getName()); entity.setPrice(product.getPrice()); entity.setStock(product.getStock()); return entity; } }
2.3 優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):
- 結(jié)構(gòu)清晰,嚴(yán)格遵循六邊形架構(gòu)原則
- 領(lǐng)域模型完全獨(dú)立,不受技術(shù)框架影響
- 適配器隔離了所有外部依賴
- 高度可測(cè)試,可以輕松模擬任何外部組件
缺點(diǎn):
- 代碼量較大,需要編寫更多的接口和適配器
- 對(duì)象映射工作增加,需要在適配器中轉(zhuǎn)換領(lǐng)域?qū)ο蠛统志没瘜?duì)象
- 可能感覺過度設(shè)計(jì),特別是對(duì)簡(jiǎn)單應(yīng)用程序
- 學(xué)習(xí)曲線較陡峭,團(tuán)隊(duì)需要深入理解六邊形架構(gòu)
2.4 適用場(chǎng)景
- 復(fù)雜的業(yè)務(wù)領(lǐng)域,需要清晰隔離業(yè)務(wù)規(guī)則
- 長(zhǎng)期維護(hù)的核心系統(tǒng)
- 團(tuán)隊(duì)已熟悉六邊形架構(gòu)原則
- 需要靈活替換技術(shù)實(shí)現(xiàn)的場(chǎng)景
三、基于DDD的六邊形架構(gòu)
3.1 項(xiàng)目結(jié)構(gòu)
基于DDD的六邊形架構(gòu)結(jié)合了領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的概念,進(jìn)一步豐富了領(lǐng)域?qū)樱?/p>
src/main/java/com/example/demo/
├── domain/ # 領(lǐng)域?qū)?br />│ ├── model/ # 領(lǐng)域模型
│ │ ├── aggregate/ # 聚合
│ │ ├── entity/ # 實(shí)體
│ │ └── valueobject/ # 值對(duì)象
│ ├── service/ # 領(lǐng)域服務(wù)
│ └── repository/ # 倉(cāng)儲(chǔ)接口
├── application/ # 應(yīng)用層
│ ├── port/ # 應(yīng)用服務(wù)接口
│ │ ├── incoming/ # 輸入端口
│ │ └── outgoing/ # 輸出端口
│ └── service/ # 應(yīng)用服務(wù)實(shí)現(xiàn)
├── infrastructure/ # 基礎(chǔ)設(shè)施層
│ ├── adapter/ # 適配器
│ │ ├── incoming/ # 輸入適配器
│ │ └── outgoing/ # 輸出適配器
│ └── config/ # 配置類
└── interface/ # 接口層
├── rest/ # REST接口
├── graphql/ # GraphQL接口
└── scheduler/ # 定時(shí)任務(wù)
3.2 代碼實(shí)現(xiàn)
3.2.1 領(lǐng)域模型
// 值對(duì)象 package com.example.demo.domain.model.valueobject; public class Money { private final BigDecimal amount; private final String currency; public Money(BigDecimal amount, String currency) { this.amount = amount; this.currency = currency; } public Money add(Money other) { if (!this.currency.equals(other.currency)) { throw new IllegalArgumentException("Cannot add different currencies"); } return new Money(this.amount.add(other.amount), this.currency); } // 其他值對(duì)象方法 } // 實(shí)體 package com.example.demo.domain.model.entity; import com.example.demo.domain.model.valueobject.Money; public class Product { private ProductId id; private String name; private Money price; private int stock; // 構(gòu)造函數(shù)、getter和setter // 領(lǐng)域行為 public boolean isAvailable() { return stock > 0; } public void decreaseStock(int quantity) { if (quantity > stock) { throw new IllegalArgumentException("Not enough stock"); } this.stock -= quantity; } } // 聚合根 package com.example.demo.domain.model.aggregate; import com.example.demo.domain.model.entity.Product; import com.example.demo.domain.model.valueobject.Money; import java.util.ArrayList; import java.util.List; public class Order { private OrderId id; private CustomerId customerId; private List<OrderLine> orderLines = new ArrayList<>(); private OrderStatus status; private Money totalAmount; // 構(gòu)造函數(shù)、getter和setter // 領(lǐng)域行為 public void addProduct(Product product, int quantity) { if (!product.isAvailable() || product.getStock() < quantity) { throw new IllegalArgumentException("Product not available"); } OrderLine orderLine = new OrderLine(product.getId(), product.getPrice(), quantity); orderLines.add(orderLine); recalculateTotal(); } public void confirm() { if (orderLines.isEmpty()) { throw new IllegalStateException("Cannot confirm empty order"); } this.status = OrderStatus.CONFIRMED; } private void recalculateTotal() { this.totalAmount = orderLines.stream() .map(OrderLine::getSubtotal) .reduce(Money.ZERO, Money::add); } }
3.2.2 領(lǐng)域倉(cāng)儲(chǔ)接口
// 倉(cāng)儲(chǔ)接口 package com.example.demo.domain.repository; import com.example.demo.domain.model.aggregate.Order; import com.example.demo.domain.model.aggregate.OrderId; import java.util.Optional; public interface OrderRepository { Optional<Order> findById(OrderId id); Order save(Order order); void delete(OrderId id); }
3.2.3 應(yīng)用服務(wù)接口
// 應(yīng)用服務(wù)接口 package com.example.demo.application.port.incoming; import com.example.demo.application.dto.OrderRequest; import com.example.demo.application.dto.OrderResponse; import java.util.List; import java.util.Optional; public interface OrderApplicationService { OrderResponse createOrder(OrderRequest request); Optional<OrderResponse> getOrder(String orderId); List<OrderResponse> getCustomerOrders(String customerId); void confirmOrder(String orderId); }
3.2.4 應(yīng)用服務(wù)實(shí)現(xiàn)
// 應(yīng)用服務(wù)實(shí)現(xiàn) package com.example.demo.application.service; import com.example.demo.application.dto.OrderRequest; import com.example.demo.application.dto.OrderResponse; import com.example.demo.application.port.incoming.OrderApplicationService; import com.example.demo.application.port.outgoing.ProductRepository; import com.example.demo.domain.model.aggregate.Order; import com.example.demo.domain.model.aggregate.OrderId; import com.example.demo.domain.model.entity.Product; import com.example.demo.domain.repository.OrderRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @Service public class OrderApplicationServiceImpl implements OrderApplicationService { private final OrderRepository orderRepository; private final ProductRepository productRepository; public OrderApplicationServiceImpl(OrderRepository orderRepository, ProductRepository productRepository) { this.orderRepository = orderRepository; this.productRepository = productRepository; } @Override @Transactional public OrderResponse createOrder(OrderRequest request) { // 創(chuàng)建訂單領(lǐng)域?qū)ο? Order order = new Order(new CustomerId(request.getCustomerId())); // 添加產(chǎn)品 for (OrderRequest.OrderItem item : request.getItems()) { Product product = productRepository.findById(new ProductId(item.getProductId())) .orElseThrow(() -> new RuntimeException("Product not found")); order.addProduct(product, item.getQuantity()); // 減少庫(kù)存 product.decreaseStock(item.getQuantity()); productRepository.save(product); } // 保存訂單 Order savedOrder = orderRepository.save(order); // 返回DTO return mapToDto(savedOrder); } @Override public Optional<OrderResponse> getOrder(String orderId) { return orderRepository.findById(new OrderId(orderId)) .map(this::mapToDto); } @Override public List<OrderResponse> getCustomerOrders(String customerId) { return orderRepository.findByCustomerId(new CustomerId(customerId)).stream() .map(this::mapToDto) .collect(Collectors.toList()); } @Override @Transactional public void confirmOrder(String orderId) { Order order = orderRepository.findById(new OrderId(orderId)) .orElseThrow(() -> new RuntimeException("Order not found")); order.confirm(); orderRepository.save(order); } private OrderResponse mapToDto(Order order) { // 映射邏輯 } }
3.2.5 輸入適配器(REST控制器)
// REST控制器 package com.example.demo.interface.rest; import com.example.demo.application.dto.OrderRequest; import com.example.demo.application.dto.OrderResponse; import com.example.demo.application.port.incoming.OrderApplicationService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/orders") public class OrderController { private final OrderApplicationService orderService; public OrderController(OrderApplicationService orderService) { this.orderService = orderService; } @PostMapping public ResponseEntity<OrderResponse> createOrder(@RequestBody OrderRequest request) { OrderResponse response = orderService.createOrder(request); return ResponseEntity.ok(response); } @GetMapping("/{orderId}") public ResponseEntity<OrderResponse> getOrder(@PathVariable String orderId) { return orderService.getOrder(orderId) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @GetMapping("/customer/{customerId}") public List<OrderResponse> getCustomerOrders(@PathVariable String customerId) { return orderService.getCustomerOrders(customerId); } @PostMapping("/{orderId}/confirm") public ResponseEntity<Void> confirmOrder(@PathVariable String orderId) { orderService.confirmOrder(orderId); return ResponseEntity.ok().build(); } }
3.2.6 輸出適配器(JPA持久化)
// JPA實(shí)體 package com.example.demo.infrastructure.adapter.outgoing.persistence.entity; import javax.persistence.*; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @Entity @Table(name = "orders") public class OrderJpaEntity { @Id private String id; private String customerId; @Enumerated(EnumType.STRING) private OrderStatusJpa status; private BigDecimal totalAmount; private String currency; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "order_id") private List<OrderLineJpaEntity> orderLines = new ArrayList<>(); // 構(gòu)造函數(shù)、getter和setter } // JPA倉(cāng)庫(kù) package com.example.demo.infrastructure.adapter.outgoing.persistence.repository; import com.example.demo.infrastructure.adapter.outgoing.persistence.entity.OrderJpaEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import java.util.List; public interface OrderJpaRepository extends JpaRepository<OrderJpaEntity, String> { List<OrderJpaEntity> findByCustomerId(String customerId); } // 適配器實(shí)現(xiàn) package com.example.demo.infrastructure.adapter.outgoing.persistence; import com.example.demo.domain.model.aggregate.Order; import com.example.demo.domain.model.aggregate.OrderId; import com.example.demo.domain.model.entity.CustomerId; import com.example.demo.domain.repository.OrderRepository; import com.example.demo.infrastructure.adapter.outgoing.persistence.entity.OrderJpaEntity; import com.example.demo.infrastructure.adapter.outgoing.persistence.repository.OrderJpaRepository; import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @Repository public class OrderRepositoryAdapter implements OrderRepository { private final OrderJpaRepository jpaRepository; public OrderRepositoryAdapter(OrderJpaRepository jpaRepository) { this.jpaRepository = jpaRepository; } @Override public Optional<Order> findById(OrderId id) { return jpaRepository.findById(id.getValue()) .map(this::mapToDomain); } @Override public Order save(Order order) { OrderJpaEntity entity = mapToJpaEntity(order); OrderJpaEntity savedEntity = jpaRepository.save(entity); return mapToDomain(savedEntity); } @Override public void delete(OrderId id) { jpaRepository.deleteById(id.getValue()); } @Override public List<Order> findByCustomerId(CustomerId customerId) { return jpaRepository.findByCustomerId(customerId.getValue()).stream() .map(this::mapToDomain) .collect(Collectors.toList()); } private Order mapToDomain(OrderJpaEntity entity) { // 映射邏輯 } private OrderJpaEntity mapToJpaEntity(Order order) { // 映射邏輯 } }
3.3 優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):
- 結(jié)合DDD概念,更豐富的領(lǐng)域模型
- 更精確地表達(dá)業(yè)務(wù)規(guī)則和約束
- 領(lǐng)域模型與持久化完全分離
- 支持復(fù)雜業(yè)務(wù)場(chǎng)景和領(lǐng)域行為
缺點(diǎn):
- 架構(gòu)復(fù)雜度進(jìn)一步增加
- 學(xué)習(xí)曲線陡峭,需要同時(shí)掌握DDD和六邊形架構(gòu)
- 對(duì)象映射工作更加繁重
- 可能過度設(shè)計(jì),特別是對(duì)簡(jiǎn)單領(lǐng)域
3.4 適用場(chǎng)景
- 復(fù)雜業(yè)務(wù)領(lǐng)域,有豐富的業(yè)務(wù)規(guī)則和約束
- 大型企業(yè)應(yīng)用,特別是核心業(yè)務(wù)系統(tǒng)
- 團(tuán)隊(duì)熟悉DDD和六邊形架構(gòu)
- 長(zhǎng)期維護(hù)的系統(tǒng),需要適應(yīng)業(yè)務(wù)變化
四、簡(jiǎn)化版架構(gòu)實(shí)現(xiàn)
4.1 項(xiàng)目結(jié)構(gòu)
簡(jiǎn)化架構(gòu)采用更輕量級(jí)的方式實(shí)現(xiàn)六邊形架構(gòu)的核心理念,減少接口數(shù)量,簡(jiǎn)化層次結(jié)構(gòu):
src/main/java/com/example/demo/
├── service/ # 服務(wù)層
│ ├── business/ # 業(yè)務(wù)服務(wù)
│ ├── model/ # 數(shù)據(jù)模型
│ └── exception/ # 業(yè)務(wù)異常
├── integration/ # 集成層
│ ├── database/ # 數(shù)據(jù)庫(kù)集成
│ ├── messaging/ # 消息集成
│ └── external/ # 外部服務(wù)集成
├── web/ # Web層
│ ├── controller/ # 控制器
│ ├── dto/ # 數(shù)據(jù)傳輸對(duì)象
│ └── advice/ # 全局異常處理
└── config/ # 配置
4.2 代碼實(shí)現(xiàn)
4.2.1 數(shù)據(jù)模型
// 業(yè)務(wù)模型 package com.example.demo.service.model; import lombok.Data; @Data public class Product { private Long id; private String name; private double price; private int stock; // 業(yè)務(wù)邏輯直接在模型中 public boolean isAvailable() { return stock > 0; } public void decreaseStock(int quantity) { if (quantity > stock) { throw new IllegalArgumentException("Not enough stock"); } this.stock -= quantity; } }
4.2.2 集成層接口
// 數(shù)據(jù)庫(kù)集成接口 package com.example.demo.integration.database; import com.example.demo.service.model.Product; import java.util.List; import java.util.Optional; // 沒有復(fù)雜的端口和適配器分離,直接定義操作接口 public interface ProductRepository { List<Product> findAll(); Optional<Product> findById(Long id); Product save(Product product); void deleteById(Long id); }
4.2.3 業(yè)務(wù)服務(wù)
// 業(yè)務(wù)服務(wù) package com.example.demo.service.business; import com.example.demo.integration.database.ProductRepository; import com.example.demo.service.model.Product; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; @Service public class ProductService { private final ProductRepository productRepository; // 直接注入所需依賴 public ProductService(ProductRepository productRepository) { this.productRepository = productRepository; } public List<Product> getAllProducts() { return productRepository.findAll(); } public Optional<Product> getProductById(Long id) { return productRepository.findById(id); } public Product createProduct(Product product) { return productRepository.save(product); } @Transactional public void updateStock(Long productId, int quantity) { Product product = productRepository.findById(productId) .orElseThrow(() -> new RuntimeException("Product not found")); product.decreaseStock(quantity); productRepository.save(product); } }
4.2.4 控制器
// REST控制器 package com.example.demo.web.controller; import com.example.demo.service.business.ProductService; import com.example.demo.service.model.Product; import com.example.demo.web.dto.ProductDTO; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.stream.Collectors; @RestController @RequestMapping("/api/products") public class ProductController { private final ProductService productService; public ProductController(ProductService productService) { this.productService = productService; } @GetMapping public List<ProductDTO> getAllProducts() { return productService.getAllProducts().stream() .map(this::toDto) .collect(Collectors.toList()); } @GetMapping("/{id}") public ResponseEntity<ProductDTO> getProductById(@PathVariable Long id) { return productService.getProductById(id) .map(this::toDto) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @PostMapping public ProductDTO createProduct(@RequestBody ProductDTO productDto) { Product product = toEntity(productDto); return toDto(productService.createProduct(product)); } @PutMapping("/{id}/stock") public ResponseEntity<Void> updateStock( @PathVariable Long id, @RequestParam int quantity) { productService.updateStock(id, quantity); return ResponseEntity.ok().build(); } private ProductDTO toDto(Product product) { ProductDTO dto = new ProductDTO(); dto.setId(product.getId()); dto.setName(product.getName()); dto.setPrice(product.getPrice()); dto.setStock(product.getStock()); return dto; } private Product toEntity(ProductDTO dto) { Product product = new Product(); product.setId(dto.getId()); product.setName(dto.getName()); product.setPrice(dto.getPrice()); product.setStock(dto.getStock()); return product; } }
4.2.5 數(shù)據(jù)庫(kù)實(shí)現(xiàn)
// JPA實(shí)體 - 與業(yè)務(wù)模型類似 package com.example.demo.integration.database.entity; import lombok.Data; import javax.persistence.*; @Entity @Table(name = "products") @Data public class ProductEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private double price; private int stock; } // JPA倉(cāng)庫(kù) package com.example.demo.integration.database.repository; import com.example.demo.integration.database.entity.ProductEntity; import org.springframework.data.jpa.repository.JpaRepository; public interface JpaProductRepository extends JpaRepository<ProductEntity, Long> { } // 倉(cāng)庫(kù)實(shí)現(xiàn) package com.example.demo.integration.database; import com.example.demo.integration.database.entity.ProductEntity; import com.example.demo.integration.database.repository.JpaProductRepository; import com.example.demo.service.model.Product; import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @Repository public class ProductRepositoryImpl implements ProductRepository { private final JpaProductRepository jpaRepository; public ProductRepositoryImpl(JpaProductRepository jpaRepository) { this.jpaRepository = jpaRepository; } @Override public List<Product> findAll() { return jpaRepository.findAll().stream() .map(this::toModel) .collect(Collectors.toList()); } @Override public Optional<Product> findById(Long id) { return jpaRepository.findById(id) .map(this::toModel); } @Override public Product save(Product product) { ProductEntity entity = toEntity(product); return toModel(jpaRepository.save(entity)); } @Override public void deleteById(Long id) { jpaRepository.deleteById(id); } private Product toModel(ProductEntity entity) { Product product = new Product(); product.setId(entity.getId()); product.setName(entity.getName()); product.setPrice(entity.getPrice()); product.setStock(entity.getStock()); return product; } private ProductEntity toEntity(Product product) { ProductEntity entity = new ProductEntity(); entity.setId(product.getId()); entity.setName(product.getName()); entity.setPrice(product.getPrice()); entity.setStock(product.getStock()); return entity; } }
4.3 優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):
- 結(jié)構(gòu)簡(jiǎn)單,學(xué)習(xí)曲線平緩
- 減少了接口和層次數(shù)量,代碼量更少
- 遵循Spring框架慣例,對(duì)Spring開發(fā)者友好
- 開發(fā)效率高,適合快速迭代
- 仍然保持了業(yè)務(wù)邏輯和外部依賴的基本分離
缺點(diǎn):
- 分離不如經(jīng)典六邊形架構(gòu)嚴(yán)格
- 業(yè)務(wù)邏輯可能會(huì)混入非核心關(guān)注點(diǎn)
- 領(lǐng)域模型不夠豐富
- 對(duì)復(fù)雜業(yè)務(wù)場(chǎng)景支持有限
4.4 適用場(chǎng)景
- 中小型應(yīng)用,業(yè)務(wù)邏輯相對(duì)簡(jiǎn)單
- 需要快速開發(fā)和迭代的項(xiàng)目
- 原型或MVP開發(fā)
- 啟動(dòng)階段的項(xiàng)目,后期可能演進(jìn)到更嚴(yán)格的架構(gòu)
五、總結(jié)
六邊形架構(gòu)的核心價(jià)值在于將業(yè)務(wù)邏輯與技術(shù)細(xì)節(jié)分離,提高系統(tǒng)的可維護(hù)性、可測(cè)試性和靈活性。
無(wú)論選擇哪種實(shí)現(xiàn)方式,都應(yīng)該堅(jiān)持這一核心原則,保持領(lǐng)域模型的純粹性和邊界的清晰性。
需要特別說(shuō)明的是,架構(gòu)應(yīng)該服務(wù)于業(yè)務(wù),而非相反。選擇合適的架構(gòu)方式,應(yīng)以提高開發(fā)效率、系統(tǒng)質(zhì)量和業(yè)務(wù)適應(yīng)性為目標(biāo)。
以上就是SpringBoot實(shí)現(xiàn)六邊形架構(gòu)的三種不同方式詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot六邊形架構(gòu)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java學(xué)習(xí)手冊(cè)之Filter和Listener使用方法
這篇文章主要介紹了Java學(xué)習(xí)手冊(cè)之Filter?和?Listener使用方法的相關(guān)資料,Filter是一種攔截器,可以在請(qǐng)求到達(dá)Servlet之前或響應(yīng)返回客戶端之前對(duì)請(qǐng)求和響應(yīng)進(jìn)行攔截和處理,Listener用于監(jiān)聽JavaWeb應(yīng)用中的各種事件,需要的朋友可以參考下2025-04-04Java用數(shù)組實(shí)現(xiàn)循環(huán)隊(duì)列的示例
下面小編就為大家?guī)?lái)一篇Java用數(shù)組實(shí)現(xiàn)循環(huán)隊(duì)列的示例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2017-09-09Java中的數(shù)組排序方式(快速排序、冒泡排序、選擇排序)
這篇文章主要介紹了Java中的數(shù)組排序方式(快速排序、冒泡排序、選擇排序),需要的朋友可以參考下2014-02-02Java實(shí)現(xiàn)在線五子棋對(duì)戰(zhàn)游戲(人機(jī)對(duì)戰(zhàn))
這篇文章主要為大家詳細(xì)介紹了如何利用Java語(yǔ)言實(shí)現(xiàn)在線五子棋對(duì)戰(zhàn)游戲(人機(jī)對(duì)戰(zhàn)),文中的實(shí)現(xiàn)步驟講解詳細(xì),感興趣的可以嘗試一下2022-09-09Java 兩種延時(shí)thread和timer詳解及實(shí)例代碼
這篇文章主要介紹了Java 兩種延時(shí)thread和timer詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02Mybatis配置之<typeAliases>別名配置元素解析
這篇文章主要介紹了Mybatis配置之<typeAliases>別名配置元素解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07MyBatis處理mysql主鍵自動(dòng)增長(zhǎng)出現(xiàn)的不連續(xù)問題解決
本文主要介紹了MyBatis處理mysql主鍵自動(dòng)增長(zhǎng)出現(xiàn)的不連續(xù)問題解決,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09JavaSwing實(shí)現(xiàn)小型學(xué)生管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了JavaSwing實(shí)現(xiàn)小型學(xué)生管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02