本篇隨筆用於記錄我在學習 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。
個人公衆號《有刻》,咱們共同成長!