微服務框架Spring Cloud介紹 Part3: Mysteam項目結構與開發用戶註冊服務

原文地址:http://skaka.me/blog/2016/08/10/springcloud3/

上一篇文章中我們簡單的瞭解了一下Spring Cloud. 因爲Spring Cloud相關的內容較多, 所以我建了一個項目mysteam來演示Spring Cloud的使用, GitHub地址.

1. 項目結構

這是一個Maven項目, 下載下來之後直接導入IDE, 你會看到如下的項目結構(我用的是Intellij IDEA): 

普通目錄:
docs: 存放文檔資料, 例如數據庫腳本, astah文件(UML工具)等.
logs: 運行日誌存放目錄.
公共模塊:
apiutils: api模塊公共父模塊.
common: 服務模塊公共父模塊, 存放微服務共同依賴的邏輯, 例如事件處理, 定時任務等.
utils: 工具類模塊.
基礎服務模塊:
eureka: eureka服務. 提供服務註冊與服務發現. 這個服務之後會有專門的文章來介紹.
config: config服務. 提供配置管理服務. 這個服務之後會有專門的文章來介紹.
turbine: hystrix服務監控. 這個服務之後會有專門的文章來介紹.
服務模塊:
account: 賬戶服務.
coupon: 優惠券服務.
order: 訂單服務.
product: 產品服務.
user: 用戶服務.
其他模塊:
integration-test: 集成測試模塊.

這些模塊內部的項目結構大多類似, 以服務模塊user爲例.
api: api接口模塊. 其他依賴user服務的服務會依賴這個模塊.
core: user服務實現模塊.
api和core模塊內容都是標準的maven項目結構, 其中core模塊主要有這麼一些子目錄:
context: 存放Spring Boot啓動類.
dao: DAO層.
domain: Model層. service: Service層.
web: 存放Spring MVC Controller.

值得特別說明的是, 在真實的項目中, 一般每個服務都是一個獨立的項目, 彼此之間只是通過pom引用. 如果代碼都放到一個項目中, 過一段時間你會發現每次打開IDE都是件痛苦的事情, 而且IDE運行速度會奇慢無比. 這樣做也違背了微服務開發的本意: 各個服務之間相對獨立. mysteam把所有的服務都放到一個項目中只是爲了方便演示和運行. 如果你想將mysteam的模塊都拆到獨立項目中去也是相當的簡單, 只要修改pom文件即可.

好了, 項目結構介紹完, 接下來我們要做點正事了: ) 實現用戶註冊服務.

2. 實現Model

用戶表的結構相當簡單, 只有三個字段. sql文件在$YOUR_PATH/mysteam/user/docs/user-service.sql. 我們首先創建實體類. 文件位置在$YOUR_PATH/mysteam/user/core/src/main/java/com/akkafun/user/domain/User.java.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Entity
@Table(name = "user")
public class User extends VersionEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column
    private String username;

    @Column
    private String password;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }


}

實體類很簡單, 使用的是JPA註解, 繼承抽象基類VersionEntity來獲得樂觀鎖控制功能.

3. 實現DAO

DAO層使用的是Spring Data JPA,
目錄在$YOUR_PATH/mysteam/user/core/src/main/java/com/akkafun/user/dao, DAO相對簡單也不是重點, 這裏就不介紹了.

4. 實現Service

Service類是$YOUR_PATH/mysteam/user/core/src/main/java/com/akkafun/user/service/UserService.java, 我們看一下用戶註冊的業務邏輯:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Transactional
public User register(RegisterDto registerDto) {
    if(isUsernameExist(registerDto.getUsername(), Optional.empty())) {                         //1
        throw new AppBusinessException(UserErrorCode.UsernameExist,
                String.format("用戶名%s已存在", registerDto.getUsername()));
    }

    User user = new User();
    user.setUsername(registerDto.getUsername());
    try {
        user.setPassword(PasswordHash.createHash(registerDto.getPassword()));
    } catch (GeneralSecurityException e) {
        logger.error("創建哈希密碼的時候發生錯誤", e);
        throw new AppBusinessException("用戶註冊失敗");
    }

    userRepository.save(user);                                                                  //2

    //用戶創建事件
    eventBus.publish(new UserCreated(user.getId(), user.getUsername(), user.getCreateTime()));  //3

    return user;
}

@Transactional(readOnly = true)
public boolean isUsernameExist(String username, Optional<Integer> userId) {
    return userRepository.isUsernameExist(username, userId);
}

1.註冊之前首先判斷用戶名是否存在, 判斷邏輯在UserRepositoryImpl類裏. 如果用戶名重複就拋出異常.
2.調用DAO的save方法持久化用戶到數據庫.
3.發送用戶創建事件.

注意register方法上有@Transactional註解, 代表事務邊界是在service層. register方法構成一個事務, 包括事件發送. 關於事件處理後續有專門的文章介紹, 這裏先略過.

5. 實現Controller

現在來看下Controller層的處理. 打開$YOUR_PATH/mysteam/user/core/src/main/java/com/akkafun/user/web/UserController.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestController
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)                  //1
public class UserController {

    @Autowired
    UserService userService;

    @RequestMapping(value = USER_REGISTER_URL, method = RequestMethod.POST)
    public UserDto register(@Valid @RequestBody RegisterDto registerDto) {    //2

        User user = userService.register(registerDto);                        //3
        UserDto userDto = new UserDto();
        userDto.setId(user.getId());
        userDto.setUsername(user.getUsername());

        return userDto;
    }


}

這就是一個很普通的Spring MVC Controller.
1. 我們的Rest服務暫且只提供json數據的請求和響應, 所以在class級別加了一個註解@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE).
2. 註冊是POST請求, 我們使用DTO對象RegisterDto來收集數據. 注意RegisterDto是user服務的api模塊提供的, 意味着其他依賴了user服務的模塊可以直接使用RegisterDto. RequestBody類使用了Java Validation註解來校驗參數的合法性.
3. 調用UserService的register方法完成註冊, 然後將User實體對象轉化成UserDto對象返回.

6. 運行

到此就開發完了. 現在我們可以啓動user服務來看一下效果(user服務運行在23101端口).
(提示: 運行下面的UserApplication之前, 需要先啓動Eureka服務和Config服務, 啓動方法請參考上一篇文章.)
打開$YOUR_PATH/mysteam/user/core/src/main/java/com/akkafun/context/web/UserApplication.java, 直接運行main方法. 項目啓動之後, 在瀏覽器訪問http://localhost:23101/swagger-ui.html, 你應該能看見如下的頁面:

這個頁面是SpringFox根據我們的Controller類, 自動生成的swagger ui頁面. 關於swagger和SpringFox, 之後會有專門的文章來介紹. 這個頁面列出了user服務下所有的api信息(暫時只有一個register), 包括url鏈接, 請求參數, 返回值等, 你也可以在Controller類中加入@ApiOperation這種Swagger註解來對接口進行更詳細的描述. 此外, 在這個頁面你還可以直接對api進行測試, 例如在registerDto參數欄填入

1
2
3
4
{
  "password": "123456",
  "username": "aaa"
}

然後點擊下面的Try it out!按鈕, 你就能看見服務器的返回結果了.

大功告成. 整個過程除去實體類的話, 真正的業務代碼只有幾十行. 代碼量雖少, 但是我們已經開發了一個完整的註冊服務, 服務不但自動生成了完整的API文檔, 同時已經能通過Eureka被其他服務調用了(下一篇文章演示). 當然, 這一切都仰仗於Spring Cloud, Netflix OSS, SpringFox, Swagger等一系列開源軟件的幫助, 程序員的生產力也因此越來越高. 看着上面的步驟, 你也許會覺得, 開發一個微服務也是相當簡單的嘛. 事實上, 我們還沒有接觸到真正的難點, 因爲服務之間還沒有交互. 下篇文章我會通過下單服務, 介紹如何進行服務之間的相互調用以及如何處理事件來保證事務完整性.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章