本篇随笔用于记录我在学习 Java 和构建 Spring Boot 项目过程当中的一些思考,包含架构、组件和部署方式等。下文仅为概要,待闲时逐一整理为详细文档。
html
开源社区如火如荼,若在当下咱们还要去重复 “造轮子” 那真是罪过罪过(固然也并不意味着全部的一切均可拿来即用,了解他,使用他,如有能力,去完善它)。所以,当咱们拿到需求的时候首先应当进行拆解,哪些模块在社区中已有比较成熟的解决方案,而后大体罗列一个粗略的所需组件列表(后续根据架构的设计和兼容状况再进行调整)。java
用于解耦实体对象的装载过程,他让咱们的编程过程更关注业务逻辑自己,其重要性毋庸多言。在 Spring Boot 中比较主流的 ORM 框架有 Spring-Data-JPA 和 MyBatis。mysql
JPA 规范的好处是咱们几乎彻底专一于业务逻辑自己,JpaRepository 中的接口可以知足大部分简单的操做逻辑了,若要扩展,咱们也能够再抽离出一个父类,添加自定义的通用 CRUD 操做,如此一来仓储层的代码变得异常优雅和简洁。如下是一个简单的实现案例:
application.yml:程序员
spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://mysql:3306/youclk?characterEncoding=utf8&useSSL=false username: root password: youclk jpa: hibernate: ddl-auto: create show-sql: true
BookRespository.java:redis
public interface BookRespository extends JpaRepository<Book,Integer> { }
Book.java:spring
@Data public class Book { private Long id; private String name; private Date createTime; }
BookRespositoryTest.java:sql
@RunWith(SpringRunner.class) @SpringBootTest public class BookRespositoryTest { @Autowired private BookRespository repository; @Test public void saveTest() { Assert.assertEquals(repository.findAll().size(),0); } }
映射获得的 sql 以下:数据库
Hibernate: select book0_.id as id1_0_, book0_.name as name2_0_, book0_.number as number3_0_ from book book0_
若是是几年前的话我确定会首选 JPA, 可是期间近两年的 EFCore 开发经历让个人选择变得谨慎。C# 是 Lambda 和 Linq 的先驱者,所以 .NET + EF 实践 Code First 着实优雅。然而在迁移 EFCore 的过程当中遇到的问题真是很多,好比说 EFCore 1.x 的时候处理 GroupBy 是全表扫描而后拿到内存中过滤。对于旧项目的迁移咱们通常没有精力去验证 ORM 映射生成的每条 SQL 语句,并且本地环境因数据基数少,测试阶段很难直观地体现出来,但部署后就悲剧了,服务和数据库一块儿都要死要死的。编程
由此引起的思考是当进行里程碑版本的升级和迁移的时候,新版本 ORM 框架所生成的 SQL 还可否彻底正确体现以前代码中的逻辑。api
在 .NET Core 中除了 EFCore 还有一个很是优秀的 ORM 框架是 Dapper,这个和 MyBatis 很是像,至关于半自动档吧,开发者能更好地掌控 SQL,但牺牲了必定的简洁。介于曾经的入坑经历,至少在查询方面我选择 MyBatis,实例以下:
BookRespository.java:
public interface BookRepository { List<Book> findAll(); @Select("SELECT name FROM book WHERE id = #{id}") @Results({ @Result(property = "name", column = "name") }) String findBookName(Long id); }
BookRepository.xml:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.youclk.demo.repositories.BookRepository" > <resultMap id="BaseResultMap" type="com.youclk.demo.domain.Book" > <id column="id" property="id" jdbcType="BIGINT" /> <result column="name" property="name" jdbcType="VARCHAR" /> <result column="createTime" property="createTime" jdbcType="DATE" /> </resultMap> <sql id="Base_Column_List" > id, name, createTime </sql> <select id="findAll" resultMap="BaseResultMap" > SELECT <include refid="Base_Column_List" /> FROM book </select> </mapper>
Application.java 中加一行 MapperScan 注解:
@SpringBootApplication() @MapperScan("com.youclk.demo.repositories") public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
以上 BookRepository.java 中分别使用了注解和 xml,虽然在 Spring 中注解是王道,但我仍是喜欢 xml 这种方式。一方面接口上写这么一堆 SQL 和返回类型看起来很难受,头重脚轻,并且没有突出重点,另外一方面在于 IDEA 中对 xml 的智能提示至关友好,效率上也不见得是写注解快多少。不过采用注解方式少了些配置文件,项目结构更优。
Java 中主流的日志框架有 JUL(java.util.logging)、Log4j、Log4j2 和 Logback 这四款,JUL 因过于简陋优先淘汰,剩下的三款都是同一个做者开发,Log4j 太旧速度慢,Log4j2 太新问题多,所以 Logback 就是最优解,对应的接口门面我选择 SLF4j。须要导入的包有: slf4j-api、ogback-classic 和 logback-core,如下是个人案例:
logback-spring.xml:
<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{35} - %msg%n</pattern> </encoder> </appender> <appender name="FILE_WARN" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>WARN</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>log/com.youclk.demo/warm/%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>90</maxHistory> </rollingPolicy> <encoder> <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{35} - %msg%n</pattern> </encoder> </appender> <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>log/com.youclk.demo/error/%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>90</maxHistory> </rollingPolicy> <encoder> <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{35} - %msg%n</pattern> </encoder> </appender> <root level="warn"> <appender-ref ref="STDOUT"/> <appender-ref ref="FILE_WARN"/> <appender-ref ref="FILE_ERROR"/> </root> </configuration>
LoggerTest.java:
@RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class LoggerTest { @Test public void testOutput() { log.error("error"); log.warn("warn"); log.info("info"); } }
以上将日志的输出整理归类,若要分布式部署,则只要每一个 Container 都挂载 log 目录即可作日志的集中管理。Logback 更详细信息可查阅 “官方文档”
Memcached 和 Redis 都老生常谈了,Redis 支持更多的数据结构和操做,而且两者性能差距不大,所以选他无疑,实现上也极其简单,以下:
@RunWith(SpringRunner.class) @SpringBootTest public class RedisTest { @Autowired private RedisTemplate redisTemplate; @Test public void testSetCache() { ValueOperations<String, Book> operations = redisTemplate.opsForValue(); operations.set("my-book",new Book(),5, TimeUnit.SECONDS); System.out.println(operations.get("my-book")); } }
另外还有一种注解的用法功能上要强大很多:
@Service @CacheConfig(cacheNames="book") public class BookService { @Autowired private BookRepository bookRepository; @Cacheable() public List<Book> findAll() { return bookRepository.findAll(); } }
程序员界一直存在着一条所谓的 “语言鄙视链”,曾经为了 “打嘴炮” 而粗略地对比过 Java 和 C#,因为未深刻探究,所以我一直以来的观念都是 C# 的语法糖比 Java 优雅太多。直到我切身感觉了使用 Java 构建项目,或许就原生的两者来讲确实是 C# 更优雅,但加上社区的力量可就很差说了。好比习惯了 C# 自动属性的我最不喜欢的就是 Java 那么一堆冗长的 get 和 set, 直到我认识了 lombok,简直汗颜啊,源码注解原来还能这么灵活地使用,由此展开只要你足够有耐心,想要什么语法糖自定义注解去实现就好。还有其余的零零总总, Java 中注解和 AOP 范式的成熟应用拓展了更多的 “编程姿式” 呵(C# 中经过反射也能作到,奈何封闭的生态致使为其造轮子的很少,现今微软已拥抱开源,给个祝福吧,互相竞争才更有意思嘛)。
回归本节主题,在 .NET 中 DDD 架构的应用可谓是至关普及,你要是不晓得领域、充血模型、事件驱动等概念都很差意思说熟悉 .NET。
这里简要归纳一下,顺便谈谈个人想法,传统 DDD 架构主要分四层,分别为:User Interface(用户界面层)、Application(应用层)、Domain(领域层) 和 Infrastructure(基础设施层)。界面层就不说了,应用层主要起协调做用,好比一个请求从用户界面层过来,应用层应当分析其须要哪几个领域模块参与,并协调他们工做,但其自己不该包含任何的业务规则,基础设施层在实际应用中最重要的功能就是提供数据持久化机制。领域层则为整个项目的核心,其应囊括几乎所有的业务规则,咱们应当在该层根据项目需求设计领域模型,抽离出领域服务,每一个领域模块应当专一于处理其自身的核心业务逻辑,非核心的业务可封装为领域事件交由异步队列处理。其次,领域层做为核心,他不该该对其余层有所依赖,所以通常他会包含基础设施层的实现接口。
以前对于领域模块中的通用逻辑或非核心业务,我一般的处理方案是封装为领域事件分发,如今想一想如此作法不合理之处,领域事件有些被滥用了。介于 AOP 在 Spring Boot 的普遍应用,领域模型中除了领域实体、值对象、领域服务、领域事件和工做单元以外再加一个领域切面也是极好的。另外,对于领域实体最后的持久化操做若是使用 MyBatis 此类的 ORM 框架那整个编程过程就变得至关繁琐,在领域中比较容易作到的是对实体状态的跟踪,所以持久化选择 JPA 规范的 ORM 框架才更为合理,但在查询上我更喜欢 MyBatis,所以若要作读写分离的话, JPA 和 MyBatis 分别对应主备数据库操做正好。
自从习惯了 Docker 以后,我已经不适应服务的单独部署了,具体操做详见个人这篇博文:“Compose & Swarm”。
近期正在寻觅 “全栈” 或 “Java 开发” 的工做岗位,如有意向,欢迎留言,微信: youclk。
个人公众号《有刻》,咱们共同成长!