首页 > > > 你还在从零搭建项目 ?
阅读本文大概需要 16 分钟。
前言
在我的工作中,我从零开始搭建了不少软件项目,其中包含了基础代码框架和持续集成基础设施等,这些内容在敏捷开发中通常被称为“第0个迭代”要做的事情。但是,当项目运行了一段时间之后再来反观,我总会发现一些不足的地方,要么测试分类没有分好,要么基本的编码架子没有考虑周全。
另外,我在工作中也会接触到很多既有项目,公司内部和外部的都有,多数项目的编码实践我都是不满意的。比如,我曾经新加入一个项目的时候,前前后后请教了3位同事才把该项目在本地运行起来;又比如在另一项目中,我发现前端请求对应的Java类命名规范不统一,有被后缀为Request的,也有被后缀为Command的。
再者,工作了这么多年之后,我越来越发现基础知识以及系统性学习的重要性。诚然,技术框架的发展使得我们可以快速地实现业务功能,但是当软件出了问题之后有时却需要将各方面的知识融会贯通并在大脑里综合反应才能找到解决思路。
基于以上,我希望整理出一套公共性的项目模板出来,旨在尽量多地包含日常开发之所需,减少开发者的重复性工作以及提供一些最佳实践。对于后端开发而言,我选择了当前被行业大量使用的Spring Boot,基于此整理出了一套公共的、基础性的实践方式,在结合了自己的经验以及其他项目的优秀实践之后,总结出本文以飨开发者。
本文以一个简单的电商订单系统为例,源代码请访问:
git clone https://github.com/e-commerce-sample/order-backend
git checkout a443dace
所使用的技术栈主要包括:Spring Boot、Gradle、MySQL、Junit 5、Rest Assured、Docker等。
一份好的README可以给人以项目全景概览,可以使新人快速上手项目,可以降低沟通成本。同时,README应该简明扼要,条理清晰,建议包含以下方面:
https://www.thoughtworks.com/radar/techniques/lightweight-architecture-decision-records
#!/usr/bin/env bash./gradlew clean bootRun
└── order-backend├── gradle // 文件夹,用于放置所有Gradle配置├── src // 文件夹,Java源代码├── idea.sh //生成IntelliJ工程├── local-build.sh // 提交之前的本地构建└── run.sh // 本地运行
├── gradle│ ├── checkstyle│ │ ├── checkstyle.gradle│ │ └── checkstyle.xml
├── order│ ├── OrderApplicationService.java│ ├── OrderController.java│ ├── OrderNotFoundException.java│ ├── OrderRepository.java│ ├── OrderService.java│ └── model│ ├── Order.java│ ├── OrderFactory.java│ ├── OrderId.java│ ├── OrderItem.java│ └── OrderStatus.java
└── product├── Product.java├── ProductApplicationService.java├── ProductController.java├── ProductId.java└── ProductRepository.java
└── common├── configuration├── exception├── loggin└── utils
sourceSets {componentTest {compileClasspath += sourceSets.main.output + sourceSets.test.outputruntimeClasspath += sourceSets.main.output + sourceSets.test.output}apiTest {compileClasspath += sourceSets.main.output + sourceSets.test.outputruntimeClasspath += sourceSets.main.output + sourceSets.test.output}}
apply plugin: 'docker-compose'dockerCompose {useComposeFiles = ['docker/mysql/docker-compose.yml']}bootRun.dependsOn composeUpcomponentTest.dependsOn composeUpapiTest.dependsOn composeUp
https://www.cnblogs.com/CloudTeng/p/3417762.html
protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain)throws ServletException, IOException {//request id in header may come from Gateway, eg. NginxString headerRequestId = request.getHeader(HEADER_X_REQUEST_ID);MDC.put(REQUEST_ID, isNullOrEmpty(headerRequestId) ? newUuid() : headerRequestId);try {filterChain.doFilter(request, response);} finally {clearMdc();}}
<appender name="REDIS" class="com.cwbase.logback.RedisAppender"><tags>ecommerce-order-backend-${ACTIVE_PROFILE}</tags><host>elk.yourdomain.com</host><port>6379</port><password>whatever</password><key>ecommerce-ordder-log</key><mdc>true</mdc><type>redis</type></appender>
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()));}}
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"}}}
@Configuration@EnableAsync@EnableSchedulingpublic class SchedulingConfiguration implements SchedulingConfigurer {public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {taskRegistrar.setScheduler(newScheduledThreadPool(10));}(destroyMethod = "shutdown")public TaskExecutor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(2);executor.setMaxPoolSize(5);executor.setQueueCapacity(10);executor.setTaskDecorator(new LogbackMdcTaskDecorator());executor.initialize();return executor;}}
@Configuration(defaultLockAtMostFor = "PT30S")public class DistributedLockConfiguration {public LockProvider lockProvider(DataSource dataSource) {return new JdbcTemplateLockProvider(dataSource);}public DistributedLockExecutor distributedLockExecutor(LockProvider lockProvider) {return new DistributedLockExecutor(lockProvider);}}
@Scheduled(cron = "0 0/1 * * * ?")@SchedulerLock(name = "scheduledTask", lockAtMostFor = THIRTY_MIN, lockAtLeastFor = ONE_MIN)public void run() {logger.info("Run scheduled task.");}为了支持代码直接调用分布式锁,基于Shedlock的LockProvider创建DistributedLockExecutor:public class DistributedLockExecutor {private final LockProvider lockProvider;public DistributedLockExecutor(LockProvider lockProvider) {this.lockProvider = lockProvider;}public <T> T executeWithLock(Supplier<T> supplier, LockConfiguration configuration) {Optional<SimpleLock> lock = lockProvider.lock(configuration);if (!lock.isPresent()) {throw new LockAlreadyOccupiedException(configuration.getName());}try {return supplier.get();} finally {lock.get().unlock();}}}
public String doBusiness() {return distributedLockExecutor.executeWithLock(() -> "Hello World.",new LockConfiguration("key", Instant.now().plusSeconds(60)));}
./run.sh
{requestId: "698c8d29add54e24a3d435e2c749ea00",buildNumber: "unknown",buildTime: "unknown",deployTime: "2019-04-11T13:05:46.901+08:00[Asia/Shanghai]",gitRevision: "unknown",gitBranch: "unknown",environment: "[local]"}
@Configuration2(value = {"local", "dev"})public class SwaggerConfiguration {public Docket api() {return new Docket(SWAGGER_2).select().apis(basePackage("com.ecommerce.order")).paths(any()).build();}}

resources/├── db│ └── migration│ ├── V1__init.sql│ └── V2__create_product_table.sql
@Configurationpublic class CorsConfiguration {public WebMvcConfigurer corsConfigurer() {return new WebMvcConfigurer() {public void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**");}};}}
@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {protected void configure(HttpSecurity http) throws Exception {http// by default uses a Bean by the name of corsConfigurationSource.cors().and()...}CorsConfigurationSource corsConfigurationSource() {CorsConfiguration configuration = new CorsConfiguration();configuration.setAllowedOrigins(Arrays.asList("https://example.com"));configuration.setAllowedMethods(Arrays.asList("GET","POST"));UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", configuration);return source;}}
往期精彩回顾
IntelliJ IDEA 2019从入门到癫狂 图文教程!

朕已阅 
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69902700/viewspace-3166461/ ,如需转载,请注明出处,否则将追究法律责任。