第 3 章 使用 ASP.NET Core 开发微服务

微服务定义

微服务是一个支持特定业务场景的独立部署单元。它借助语义化版本管理、定义良好的 API 与其他后端服务交互。它的天然特点就是严格遵守单一职责原则。

为什么要用 API 优先

所有团队都一致把公开、文档完备且语义化版本管理的 API 作为稳定的契约予以遵守,那么这种契约也能让各团队自主地掌握其发布节奏。遵循语义化版本规则能让团队在完善 API 的同时,不破坏已有消费方使用的 API。

作为微服务生态系统成功的基石,坚持好 API 优先的这些实践,远比开发服务所用的技术或代码更重要。

以测试优先的方式开发控制器

每一个单元测试方法都包含如下三个部分:

  • 安排(Arrange)完成准备测试的必要配置

  • 执行(Act)执行被测试的代码

  • 断言(Assert)验证测试条件并确定测试是否通过

测试项目:
https://github.com/microservices-aspnetcore/teamservice

特别注意测试项目如何把其他项目引用进来,以及为什么不需要再次声明从主项目继承而来的依赖项。

StatlerWaldorfCorp.TeamService.Tests.csproj

Exenetcoreapp1.1

首先创建 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;}}
}

每个团队都需要一系列成员对象

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;}}
}

创建第一个失败的测试

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());}}
}

要查看测试运行失败的结果,请打开一个终端并运行 cd 浏览到对应目录,然后运行以下命令:

$ dotnet restore
$ dotnet test

因为被测试的控制器尚未创建,所以测试项目无法通过。

向主项目添加一个控制器:

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();}}
}

第一个测试通过后,我们需要添加一个新的、运行失败的断言,检查从响应里获取的团队数目是正确的,由于还没创建模拟对象,先随意选择一个数字。

List teams = new List(controller.GetAllTeams());
Assert.Equal(teams.Count, 2);

现在让我们在控制器里硬编码一些随机的逻辑,使测试通过。

只编写恰好能让测试通过的代码,这样的小迭代作为 TDD 规则的一部分,不光是一种 TDD 运作方式,更能直接提高对代码的信心级别,同时也能避免 API 逻辑膨胀。

更新后的 TeamsController 类,支持新的测试

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") };}}
}

接下来关注添加团队方法。

[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);
}

代码略粗糙,测试通过后可以重构测试以及被测试代码。

在真实世界的服务里,不应该在内存中存储数据,因为会违反云原生服务的无状态规则。

接下来创建一个接口表示仓储,并重构控制器来使用它。

ITeamRepository.cs

using System.Collections.Generic;namespace StatlerWaldorfCorp.TeamService.Persistence
{public interface ITeamRepository {IEnumerable GetTeams();void AddTeam(Team team);}
}

在主项目中为这一仓储接口创建基于内存的实现

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 系统,我们将通过 Startup 类把仓储添加为 DI 服务

public void ConfigureServices(IServiceCollection services)
{services.AddMvc();services.AddScoped();
}

利用这种 DI 服务模型,现在我们可以在控制器里使用构造函数注入,而 ASP.NET Core 则会把仓储实例添加到所有依赖它的控制器里。

修改控制器,通过给构造函数添加一个简单参数就把它注入进来

public class TeamsController : Controller
{ITeamRepository repository;public TeamsController(ITeamRepository repo){repository = repo;}...
}

修改现有的控制器方法,将使用仓储,而不是返回硬编码数据

[HttpGet]
public async virtual Task GetAllTeams()
{return this.Ok(repository.GetTeams());
}

可从 GitHub 的 master 分支找到测试集的完整代码

要立即看这些测试的效果,请先编译服务主项目,然后转到 test/StatlerWaldorfCorp.TeamService.Tests 目录,并运行下列命令:

$ dotnet restore
$ dotnet build
$ dotnet test

集成测试

集成测试最困难的部分之一经常位于启动 Web 宿主机制的实例时所需要的技术或代码上,我们在测试中需要借助 Web 宿主机制收发完整的 HTTP 消息。

庆幸的是,这一问题已由 Microsoft.AspNetCore.TestHost.TestServer类解决。

对不同场景进行测试

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);}}
}

运行团队服务的 Docker 镜像

$ docker run -p 8080:8080 dotnetcoreseservices/teamservice

端口映射之后,就可以用 http://localhost:8080 作为服务的主机名

下面的 curl 命令会向服务的 /teams 资源发送一个 POST 请求

$ curl -H "Content-Type:application/json" \ -X POST -d \ '{"id":"e52baa63-d511-417e-9e54-7aab04286281", \ "name":"Team Zombie"}' \ http://localhost:8080/teams

它返回了一个包含了新创建团队的 JSON 正文

{"name":"Team Zombie","id":"e52baa63-d511-417e-9e54-7aab04286281","members":[]}

注意上面片段的响应部分,members 属性是一个空集合。

为确定服务在多个请求之间能够维持状态(即使目前只是基于内存列表实现),我们可以使用下面的 curl 命令

$ curl http://localhost:8080/teams
[{"name":"Team Zombie","id":"e52baa63-d511-417e-9e54-7aab04286281","members":[]}]

至此,我们已经拥有了一个功能完备的团队服务,每次 Git 提交都将触发自动化测试,将自动部署到 docker hub,并未云计算环境的调度做好准备。

《ASP.NET Core 微服务实战》-- 读书笔记(第3章)相关推荐

  1. 《ASP.NET Core 微服务实战》译者序

    最近,我将<ASP.NET Core 微服务实战>一书由英文翻译为中文.这本书是由清华大学出版社引进的,目前还处于最后的排版校对过程中,现将该书的译者序发表于此. 以下为译者译全文: &q ...

  2. 《ASP.NET Core 微服务实战》送书结果公告

    如何构建基于.NET Core和云环境下的微服务技术体系?的送书抽奖结果已经出来了: 当前只有一位同学填写了地址.其他几位同学抓紧填写,3/9 日还没有完成填写将作废,奖品可是热门的<ASP.N ...

  3. 《ASP.NET Core 微服务实战》-- 读书笔记(第10章)

    第 10 章 应用和微服务安全 云应用意味着应用运行所在的基础设施无法掌控,因此安全不能再等到事后再考虑,也不能只是检查清单上毫无意义的复选框 由于安全与云原生应用密切相关,本章将讨论安全话题,并用示 ...

  4. 《ASP.NET Core 微服务实战》-- 读书笔记(第7章)

    第 7 章 开发 ASP.NET Core Web 应用 ASP.NET Core 基础 在本章,我们将从一个命令行应用开始,并且在不借助任何模板,脚手架和向导的情况下,最终得到一个功能完整的 Web ...

  5. 《ASP.NET Core 微服务实战》-- 读书笔记(第1章 、第2章)

    译者序 微服务设计方法清晰定义了各个开发团队的业务边界,微服务框架以不同方式实现了服务之间的协作与集成. .NET Core 作为全新的 .NET 技术,它不仅完全开源.跨平台,更面向云原生开发进行了 ...

  6. 《ASP.NET Core 微服务实战》-- 读书笔记(第9章)

    第 9 章 微服务系统的配置 微服务系统中的配置需要关注更多其他方面的因素,包括: 配置值的安全读写 值变更的审计能力 配置信息源本身的韧性和可靠性 少量的环境变量难以承载大型.复杂的配置信息 应用要 ...

  7. 《ASP.NET Core 微服务实战》-- 读书笔记(第12章)

    第 12 章 设计汇总 微服务开发并不是要学习 C#.Java 或者 Go 编程--而是要学习如何开发应用以适应并充分利用弹性伸缩环境的优势,它们对托管环境没有偏好,并能瞬间启停 换句话说,我们要学习 ...

  8. 《ASP.NET Core 微服务实战》-- 读书笔记(第11章)

    第 11 章 开发实时应用和服务 在本章,我们将讨论"实时"的准确含义,以及在大部分消费者看来应该属于这一范畴的应用类型 接着,我们将探讨 WebSocket,并分析为什么传统的 ...

  9. 《ASP.NET Core 微服务实战》-- 读书笔记(第6章)

    第 6 章 事件溯源与 CQRS 在本章,我们来了解一下随着云平台一同出现的设计模式 我们先探讨事件溯源和命令查询职责分离(CQRS)背后的动机与哲学 事件溯源简介 事实由事件溯源而来 我们大脑就是一 ...

最新文章

  1. CenterNet KeyPoints 关键点训练自己的数据
  2. 搜索(Search)
  3. 牛逼!微信红包封面可以更换了!!!
  4. c语言三目运算程序,有C语言中三目运算符 联系到的 程序打trace
  5. 不能上网--只要四步判断出故障所在
  6. 用WPF山寨折线图,抄?是狠狠的抄
  7. 先定个小目标, 使用C# 开发的千万级应用
  8. linux-centos7 常用的基本命令--用户组管理
  9. android 抓取webview中的所有图片_如何一键提取PDF文档中的所有图片?
  10. mysql 时间计算函数_MySQL时间计算函数DATE_SUB()用法简介说明
  11. 联想高校AI精英挑战赛总冠军出炉!助力中国迎来智能变革
  12. 计算机保养与维护论文答辩ppt,北京交通大学毕业答辩ppt模板
  13. excel数据解析内存占用优化
  14. win7计算机开机启动项设置,如何设置WIN7开机启动项?
  15. python中@property的作用
  16. python判断字符串合法,详解Python判定IP地址合法性的三种方法 python中判断一个字符串是否是IP地址...
  17. 1m=1000ms(1秒等于一千毫秒)
  18. Linux运维学习笔记(一)
  19. [面试日记] 1,时隔五年再次开始面试
  20. 云服务器 虚拟主机 故障,云虚拟主机重启服务器

热门文章

  1. 保存现场数据和状态:onSaveInstanceState\onRestoreInstanceState\onCreate()
  2. dynamic flash xml news----滚动新闻
  3. easyui关机图标_如何在Windows 10中创建关机图标
  4. Java基类共同属性设置_多选择基类的访问属性-Java初学笔记
  5. 蓝牙mesh网络基础
  6. LinkedIn联合创始人:硅谷也就700万人,为什么能创建这么多瞩目的公司 ?
  7. [cocos2d]修改富文本文本和高度
  8. You third iOS app,这个APP要放到手机上面运行,才会成功,才会新建记录。
  9. 《飞机大战》安卓游戏开发源码(三)
  10. Windows Phone 7 系统主题颜色RGB和Hex值