参考:https://insights.thoughtworks.cn/backend-development-iteration0/ 后端开发实践——开发者的第0个迭代
代码样例:https://github.com/e-commerce-sample/order-backend
目录设计:
1、首先基于业务分包(基于聚合根)
早年的Java分包方式通常是基于技术的,比如与domain包平级的有controller包、service包和infrastructure包等。这种方式当前并不被行业所推崇,而是应该首先基于业务分包。比如,在订单示例项目中,有两个重要的领域对象Order
和Product
(在DDD中称为聚合 根),所有的业务都围绕它们展开,因此分别创建order包和product包,再分别在包下创建与之相关的各个子包。此时的order包如下:
├── order
│ ├── OrderApplicationService.java
│ ├── OrderController.java
│ ├── OrderNotFoundException.java
│ ├── OrderRepository.java
│ ├── OrderService.java
│ └── model
│ ├── Order.java
│ ├── OrderFactory.java
│ ├── OrderId.java
│ ├── OrderItem.java
│ └── OrderStatus.java
更复杂场景:如果代码结构足够的简单,那么没有必要再次进行子包的划分,
├── order
│ ├── OrderApplicationService.java
│ ├── OrderController.java
│ ├── OrderPaymentProxy.java
│ ├── OrderPaymentService.java
│ ├── OrderRepository.java
│ ├── command
│ │ ├── ChangeAddressDetailCommand.java
│ │ ├── CreateOrderCommand.java
│ │ ├── OrderItemCommand.java
│ │ ├── PayOrderCommand.java
│ │ └── UpdateProductCountCommand.java
│ ├── exception
│ │ ├── OrderCannotBeModifiedException.java
│ │ ├── OrderNotFoundException.java
│ │ ├── PaidPriceNotSameWithOrderPriceException.java
│ │ └── ProductNotInOrderException.java
│ ├── model
│ │ ├── Order.java
│ │ ├── OrderFactory.java
│ │ ├── OrderId.java
│ │ ├── OrderIdGenerator.java
│ │ ├── OrderItem.java
│ │ └── OrderStatus.java
│ └── representation
│ ├── OrderItemRepresentation.java
│ ├── OrderRepresentation.java
│ └── OrderRepresentationService.java
2、对于一些不隶属于任何业务的代码可以单独分包,比如一些util类、公共配置等。比如我们依然可以创建一个common包,下面放置了Spring公共配置、异常处理框架和日志等子包:
└── common
├── configuration
├── exception
├── loggin
└── utils
3、异常处理
在设计异常处理的框架时,需要考虑以下几点:向客户端提供格式统一的异常返回;异常信息中应该包含足够多的上下文信息,最好是结构化的数据以便于客户端解析;不同类型的异常应该包含唯一标识,以便客户端精确识别
异常两种设计形式:
1、层级异常:每种具体的异常都对应了一个异常类,这些类最终继承自某个父异常;
2、全局一个异常:再以一个字段来区分不同的异常场景
层级异常设计
// ErrorCode
枚举中包含了异常的唯一标识、HTTP状态码以及错误信息;而data
字段表示各个异常的上下文信息。
public abstract class AppException extends RuntimeException { private final ErrorCode code; private final Map<String, Object> data = newHashMap(); } /// 具体异常 public class OrderNotFoundException extends AppException { public OrderNotFoundException(OrderId orderId) { super(ErrorCode.ORDER_NOT_FOUND, ImmutableMap.of("orderId", orderId.toString())); } }
在返回异常给客户端时,通过一个ErrorDetail类来统一异常格式:
public final class ErrorDetail { private final ErrorCode code; private final int status; private final String message; private final String path; private final Instant timestamp; private final Map<String, Object> data = newHashMap(); }
样例
{ requestId: "d008ef46bb4f4cf19c9081ad50df33bd", error: { code: "ORDER_NOT_FOUND", status: 404, message: "没有找到订单", path: "/order", timestamp: 1555031270087, data: { orderId: "123456789" } } }
4、多环境构建
在软件的开发流程中,我们需要将软件部署到多个环境,经过多轮验证后才能最终上线。在不同的阶段中,软件的运行态可能是不一样的,比如本地开发时可能将所依赖的第三方系统stub掉;持续集成构建时可能使用的是测试用的内存数据库等等。为此,本文的示例项目推荐采用以下环境:
local:用于开发者本地开发
ci:用于持续集成
dev:用于前端开发联调
qa:用于测试人员
uat:类生产环境,用于功能验收(有时也称为staging环境)
prod:正式的生产环境