《ASP.NET Core 微服务实战》-- 读书笔记(第3章)
第 3 章 使用 ASP.NET Core 開發(fā)微服務(wù)
微服務(wù)定義
微服務(wù)是一個(gè)支持特定業(yè)務(wù)場(chǎng)景的獨(dú)立部署單元。它借助語義化版本管理、定義良好的 API 與其他后端服務(wù)交互。它的天然特點(diǎn)就是嚴(yán)格遵守單一職責(zé)原則。
為什么要用 API 優(yōu)先
所有團(tuán)隊(duì)都一致把公開、文檔完備且語義化版本管理的 API 作為穩(wěn)定的契約予以遵守,那么這種契約也能讓各團(tuán)隊(duì)自主地掌握其發(fā)布節(jié)奏。遵循語義化版本規(guī)則能讓團(tuán)隊(duì)在完善 API 的同時(shí),不破壞已有消費(fèi)方使用的 API。
作為微服務(wù)生態(tài)系統(tǒng)成功的基石,堅(jiān)持好 API 優(yōu)先的這些實(shí)踐,遠(yuǎn)比開發(fā)服務(wù)所用的技術(shù)或代碼更重要。
以測(cè)試優(yōu)先的方式開發(fā)控制器
每一個(gè)單元測(cè)試方法都包含如下三個(gè)部分:
-
安排(Arrange)完成準(zhǔn)備測(cè)試的必要配置
-
執(zhí)行(Act)執(zhí)行被測(cè)試的代碼
-
斷言(Assert)驗(yàn)證測(cè)試條件并確定測(cè)試是否通過
測(cè)試項(xiàng)目:
https://github.com/microservices-aspnetcore/teamservice
特別注意測(cè)試項(xiàng)目如何把其他項(xiàng)目引用進(jìn)來,以及為什么不需要再次聲明從主項(xiàng)目繼承而來的依賴項(xiàng)。
StatlerWaldorfCorp.TeamService.Tests.csproj
Exenetcoreapp1.1首先創(chuàng)建 Team 模型類
Team.cs
using System; using System.Collections.Generic;namespace StatlerWaldorfCorp.TeamService.Models {public class Team {public string Name { get; set; }public Guid ID { get; set; }public ICollection Members { get; set; }public Team(){this.Members = new List();}public Team(string name) : this(){this.Name = name;}public Team(string name, Guid id) : this(name){this.ID = id;}public override string ToString() {return this.Name;}} }每個(gè)團(tuán)隊(duì)都需要一系列成員對(duì)象
Member.cs
using System;namespace StatlerWaldorfCorp.TeamService.Models {public class Member {public Guid ID { get; set; }public string FirstName { get; set; }public string LastName { get; set; }public Member() {}public Member(Guid id) : this() {this.ID = id;}public Member(string firstName, string lastName, Guid id) : this(id) {this.FirstName = firstName;this.LastName = lastName;}public override string ToString() {return this.LastName;}} }創(chuàng)建第一個(gè)失敗的測(cè)試
TeamsControllerTest.cs
using Xunit; using System.Collections.Generic; using StatlerWaldorfCorp.TeamService.Models;namespace StatlerWaldorfCorp.TeamService {public class TeamsControllerTest{TeamsController controller = new TeamsController();[Fact]public void QueryTeamListReturnsCorrectTeams(){List teams = new List(controller.GetAllTeams());}} }要查看測(cè)試運(yùn)行失敗的結(jié)果,請(qǐng)打開一個(gè)終端并運(yùn)行 cd 瀏覽到對(duì)應(yīng)目錄,然后運(yùn)行以下命令:
$ dotnet restore $ dotnet test因?yàn)楸粶y(cè)試的控制器尚未創(chuàng)建,所以測(cè)試項(xiàng)目無法通過。
向主項(xiàng)目添加一個(gè)控制器:
TeamsController.cs
using System; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Linq; using StatlerWaldorfCorp.TeamService.Models;namespace StatlerWaldorfCorp.TeamService {public class TeamsController{public TeamsController(){}[HttpGet]public IEnumerable GetAllTeams(){return Enumerable.Empty();}} }第一個(gè)測(cè)試通過后,我們需要添加一個(gè)新的、運(yùn)行失敗的斷言,檢查從響應(yīng)里獲取的團(tuán)隊(duì)數(shù)目是正確的,由于還沒創(chuàng)建模擬對(duì)象,先隨意選擇一個(gè)數(shù)字。
List teams = new List(controller.GetAllTeams()); Assert.Equal(teams.Count, 2);現(xiàn)在讓我們?cè)诳刂破骼镉簿幋a一些隨機(jī)的邏輯,使測(cè)試通過。
只編寫恰好能讓測(cè)試通過的代碼,這樣的小迭代作為 TDD 規(guī)則的一部分,不光是一種 TDD 運(yùn)作方式,更能直接提高對(duì)代碼的信心級(jí)別,同時(shí)也能避免 API 邏輯膨脹。
更新后的 TeamsController 類,支持新的測(cè)試
using System; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Linq; using StatlerWaldorfCorp.TeamService.Models;namespace StatlerWaldorfCorp.TeamService {public class TeamsController{public TeamsController(){}[HttpGet]public IEnumerable GetAllTeams(){return new Team[] { new Team("One"), new Team("Two") };}} }接下來關(guān)注添加團(tuán)隊(duì)方法。
[Fact] public void CreateTeamAddsTeamToList() {TeamsController controller = new TeamsController();var teams = (IEnumerable)(await controller.GetAllTeams() as ObjectResult).Value;List original = new List(teams);Team t = new Team("sample");var result = controller.CreateTeam(t);var newTeamsRaw = (IEnumerable)(controller.GetAllTeams() as ObjectResult).Value;List newTeams = new List(newTeamsRaw);Assert.Equal(newTeams.Count, original.Count+1);var sampleTeam = newTeams.FirstOrDefault( target => target.Name == "sample");Assert.NotNull(sampleTeam); }代碼略粗糙,測(cè)試通過后可以重構(gòu)測(cè)試以及被測(cè)試代碼。
在真實(shí)世界的服務(wù)里,不應(yīng)該在內(nèi)存中存儲(chǔ)數(shù)據(jù),因?yàn)闀?huì)違反云原生服務(wù)的無狀態(tài)規(guī)則。
接下來創(chuàng)建一個(gè)接口表示倉(cāng)儲(chǔ),并重構(gòu)控制器來使用它。
ITeamRepository.cs
using System.Collections.Generic;namespace StatlerWaldorfCorp.TeamService.Persistence {public interface ITeamRepository {IEnumerable GetTeams();void AddTeam(Team team);} }在主項(xiàng)目中為這一倉(cāng)儲(chǔ)接口創(chuàng)建基于內(nèi)存的實(shí)現(xiàn)
MemoryTeamRepository.cs
using System.Collections.Generic;namespace StatlerWaldorfCorp.TeamService.Persistence {public class MemoryTeamRepository : ITeamRepository {protected static ICollection teams;public MemoryTeamRepository() {if(teams == null) {teams = new List();}}public MemoryTeamRepository(ICollection teams) {teams = teams;}public IEnumerable GetTeams() {return teams;}public void AddTeam(Team t){teams.Add(t);}} }借助 ASP.NET Core 的 DI 系統(tǒng),我們將通過 Startup 類把倉(cāng)儲(chǔ)添加為 DI 服務(wù)
public void ConfigureServices(IServiceCollection services) {services.AddMvc();services.AddScoped(); }利用這種 DI 服務(wù)模型,現(xiàn)在我們可以在控制器里使用構(gòu)造函數(shù)注入,而 ASP.NET Core 則會(huì)把倉(cāng)儲(chǔ)實(shí)例添加到所有依賴它的控制器里。
修改控制器,通過給構(gòu)造函數(shù)添加一個(gè)簡(jiǎn)單參數(shù)就把它注入進(jìn)來
public class TeamsController : Controller {ITeamRepository repository;public TeamsController(ITeamRepository repo){repository = repo;}... }修改現(xiàn)有的控制器方法,將使用倉(cāng)儲(chǔ),而不是返回硬編碼數(shù)據(jù)
[HttpGet] public async virtual Task GetAllTeams() {return this.Ok(repository.GetTeams()); }可從 GitHub 的 master 分支找到測(cè)試集的完整代碼
要立即看這些測(cè)試的效果,請(qǐng)先編譯服務(wù)主項(xiàng)目,然后轉(zhuǎn)到 test/StatlerWaldorfCorp.TeamService.Tests 目錄,并運(yùn)行下列命令:
$ dotnet restore $ dotnet build $ dotnet test集成測(cè)試
集成測(cè)試最困難的部分之一經(jīng)常位于啟動(dòng) Web 宿主機(jī)制的實(shí)例時(shí)所需要的技術(shù)或代碼上,我們?cè)跍y(cè)試中需要借助 Web 宿主機(jī)制收發(fā)完整的 HTTP 消息。
慶幸的是,這一問題已由 Microsoft.AspNetCore.TestHost.TestServer類解決。
對(duì)不同場(chǎng)景進(jìn)行測(cè)試
SimpleIntegrationTests.cs
using Xunit; using System.Collections.Generic; using StatlerWaldorfCorp.TeamService.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.TestHost; using System; using System.Net.Http; using System.Linq; using Newtonsoft.Json; using System.Text;namespace StatlerWaldorfCorp.TeamService.Tests.Integration {public class SimpleIntegrationTests{private readonly TestServer testServer;private readonly HttpClient testClient;private readonly Team teamZombie;public SimpleIntegrationTests(){testServer = new TestServer(new WebHostBuilder().UseStartup());testClient = testServer.CreateClient();teamZombie = new Team() {ID = Guid.NewGuid(),Name = "Zombie"};}[Fact]public async void TestTeamPostAndGet(){StringContent stringContent = new StringContent(JsonConvert.SerializeObject(teamZombie),UnicodeEncoding.UTF8,"application/json");// ActHttpResponseMessage postResponse = await testClient.PostAsync("/teams",stringContent);postResponse.EnsureSuccessStatusCode();var getResponse = await testClient.GetAsync("/teams");getResponse.EnsureSuccessStatusCode();string raw = await getResponse.Content.ReadAsStringAsync();List teams = JsonConvert.DeserializeObject>(raw);Assert.Equal(1, teams.Count());Assert.Equal("Zombie", teams[0].Name);Assert.Equal(teamZombie.ID, teams[0].ID);}} }運(yùn)行團(tuán)隊(duì)服務(wù)的 Docker 鏡像
$ docker run -p 8080:8080 dotnetcoreseservices/teamservice端口映射之后,就可以用 http://localhost:8080 作為服務(wù)的主機(jī)名
下面的 curl 命令會(huì)向服務(wù)的 /teams 資源發(fā)送一個(gè) POST 請(qǐng)求
$ curl -H "Content-Type:application/json" \ -X POST -d \ '{"id":"e52baa63-d511-417e-9e54-7aab04286281", \ "name":"Team Zombie"}' \ http://localhost:8080/teams它返回了一個(gè)包含了新創(chuàng)建團(tuán)隊(duì)的 JSON 正文
{"name":"Team Zombie","id":"e52baa63-d511-417e-9e54-7aab04286281","members":[]}注意上面片段的響應(yīng)部分,members 屬性是一個(gè)空集合。
為確定服務(wù)在多個(gè)請(qǐng)求之間能夠維持狀態(tài)(即使目前只是基于內(nèi)存列表實(shí)現(xiàn)),我們可以使用下面的 curl 命令
$ curl http://localhost:8080/teams [{"name":"Team Zombie","id":"e52baa63-d511-417e-9e54-7aab04286281","members":[]}]至此,我們已經(jīng)擁有了一個(gè)功能完備的團(tuán)隊(duì)服務(wù),每次 Git 提交都將觸發(fā)自動(dòng)化測(cè)試,將自動(dòng)部署到 docker hub,并未云計(jì)算環(huán)境的調(diào)度做好準(zhǔn)備。
總結(jié)
以上是生活随笔為你收集整理的《ASP.NET Core 微服务实战》-- 读书笔记(第3章)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 研发协同平台持续集成实践
- 下一篇: abp vnext2.0之核心组件模块加