《ASP.NET Core 微服务实战》-- 读书笔记(第3章)
第 3 章 使用 ASP.NET Core 开发微服务
微服务定义
微服务是一个支持特定业务场景的独立部署单元。它借助语义化版本管理、定义良好的 API 与其他后端服务交互。它的天然特点就是严格遵守单一职责原则。
为什么要用 API 优先
所有团队都一致把公开、文档完备且语义化版本管理的 API 作为稳定的契约予以遵守,那么这种契约也能让各团队自主地掌握其发布节奏。遵循语义化版本规则能让团队在完善 API 的同时,不破坏已有消费方使用的 API。
作为微服务生态系统成功的基石,坚持好 API 优先的这些实践,远比开发服务所用的技术或代码更重要。
以测试优先的方式开发控制器
每一个单元测试方法都包含如下三个部分:
- 安排(Arrange)完成准备测试的必要配置
- 执行(Act)执行被测试的代码
- 断言(Assert)验证测试条件并确定测试是否通过
测试项目:
https://github.com/microservices-aspnetcore/teamservice
特别注意测试项目如何把其他项目引用进来,以及为什么不需要再次声明从主项目继承而来的依赖项。
StatlerWaldorfCorp.TeamService.Tests.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../src/StatlerWaldorfCorp.TeamService/StatlerWaldorfCorp.TeamService.csproj"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170210-02" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
</ItemGroup>
</Project>
首先创建 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<Member> Members { get; set; }
public Team()
{
this.Members = new List<Member>();
}
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<Team> teams = new List<Team>(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<Team> GetAllTeams()
{
return Enumerable.Empty<Team>();
}
}
}
第一个测试通过后,我们需要添加一个新的、运行失败的断言,检查从响应里获取的团队数目是正确的,由于还没创建模拟对象,先随意选择一个数字。
List<Team> teams = new List<Team>(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<Team> GetAllTeams()
{
return new Team[] { new Team("One"), new Team("Two") };
}
}
}
接下来关注添加团队方法。
[Fact]
public void CreateTeamAddsTeamToList()
{
TeamsController controller = new TeamsController();
var teams = (IEnumerable<Team>)(await controller.GetAllTeams() as ObjectResult).Value;
List<Team> original = new List<Team>(teams);
Team t = new Team("sample");
var result = controller.CreateTeam(t);
var newTeamsRaw = (IEnumerable<Team>)(controller.GetAllTeams() as ObjectResult).Value;
List<Team> newTeams = new List<Team>(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<Team> GetTeams();
void AddTeam(Team team);
}
}
在主项目中为这一仓储接口创建基于内存的实现
MemoryTeamRepository.cs
using System.Collections.Generic;
namespace StatlerWaldorfCorp.TeamService.Persistence
{
public class MemoryTeamRepository : ITeamRepository {
protected static ICollection<Team> teams;
public MemoryTeamRepository() {
if(teams == null) {
teams = new List<Team>();
}
}
public MemoryTeamRepository(ICollection<Team> teams) {
teams = teams;
}
public IEnumerable<Team> 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<ITeamRepository, MemoryTeamRepository>();
}
利用这种 DI 服务模型,现在我们可以在控制器里使用构造函数注入,而 ASP.NET Core 则会把仓储实例添加到所有依赖它的控制器里。
修改控制器,通过给构造函数添加一个简单参数就把它注入进来
public class TeamsController : Controller
{
ITeamRepository repository;
public TeamsController(ITeamRepository repo)
{
repository = repo;
}
...
}
修改现有的控制器方法,将使用仓储,而不是返回硬编码数据
[HttpGet]
public async virtual Task<IActionResult> 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<Startup>());
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");
// Act
HttpResponseMessage postResponse = await testClient.PostAsync(
"/teams",
stringContent);
postResponse.EnsureSuccessStatusCode();
var getResponse = await testClient.GetAsync("/teams");
getResponse.EnsureSuccessStatusCode();
string raw = await getResponse.Content.ReadAsStringAsync();
List<Team> teams = JsonConvert.DeserializeObject<List<Team>>(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,并未云计算环境的调度做好准备。


本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。
如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。
《ASP.NET Core 微服务实战》-- 读书笔记(第3章)的更多相关文章
- ASP.NET Core微服务实战系列
希望给你3-5分钟的碎片化学习,可能是坐地铁.等公交,积少成多,水滴石穿,码字辛苦,如果你吃了蛋觉得味道不错,希望点个赞,谢谢关注. 前言 这里记录的是个人奋斗和成长的地方,该篇只是一个系列目录和构想 ...
- 《ASP.NET Core In Action》读书笔记系列,这是一个手把手的从零开始的教学系列目录
最近打算系统学习一下asp.net core ,苦于没有好的中文书藉,只好找来一本英文的 <ASP.NET Core In Action>学习.我和多数人一样,学习英文会明显慢于中文.希 ...
- Spring Cloud微服务实战阅读笔记(一) 基础知识
本文系<Spring Cloud微服务实战>作者:翟永超,一书的阅读笔记. 一:基础知识 1:什么是微服务架构 是一种架构设计风格,主旨是将一个原本独立的系统拆分成多个小型服务 ...
- 《ASP.NET Core In Action》读书笔记系列五 ASP.NET Core 解决方案结构解析1
创建好项目后,解决方案资源管理器窗口里我们看到,增加了不少文件夹及文件,如下图所示: 在解决方案文件夹中,找到项目文件夹,该文件夹又包含五个子文件夹 -Models.Controllers.Views ...
- 《ASP.NET Core In Action》读书笔记系列四 创建ASP.NET Core 应用步骤及相应CLI命令
一般情况下,我们都是从一个模板(template)开始创建应用的(模板:提供构建应用程序所需的基本代码).本节使用 Visual Studio 2017 .ASP.NET Core2.0和 Visua ...
- 《ASP.NET Core In Action》读书笔记系列三 ASP.NET Core如何处理请求的?
在本节中,您将看到ASP.NET Core应用程序如何运行的,从请求URL开始到页面呈现在浏览器中. 为此,您将看到 一个HTTP请求在Web服务器中是如何被处理的.ASP.NET Core如何扩展该 ...
- 《ASP.NET Core In Action》读书笔记系列二 ASP.NET Core 能用于什么样的应用,什么时候选择ASP.NET Core
ASP.NET Core 能用于什么样的应用 ASP.NET Core 可以用作传统的web服务.RESTful服务.远程过程调用(RPC)服务.微服务,这归功于它的跨平台支持和轻量级设计.如下图所示 ...
- 《ASP.NET Core In Action》读书笔记系列一 ASP.NET Core 的诞生
最近打算系统学习一下asp.net core ,苦于没有好的中文书藉,只好找来一本英文的 <ASP.NET Core In Action>学习.我和多数人一样,学习英文会明显慢于中文.希 ...
- 【.NET Core微服务实战-统一身份认证】开篇及目录索引
简介 学习.NETCORE也有1年多时间了,发现.NETCORE项目实战系列教程很少,都是介绍开源项目或基础教程,对于那些观望的朋友不能形成很好的学习思路,遇到问题怕无法得到解决而不敢再实际项目中 ...
- Spring Cloud 微服务实战笔记
Spring Cloud 微服务实战笔记 微服务知识 传统开发所有业务逻辑都在一个应用中, 开发,测试,部署随着需求增加会不断为单个项目增加不同业务模块:前端展现也不局限于html视图模板的形式,后端 ...
随机推荐
- 在vue项目中使用momentjs获取今日、昨日、本周、本月、上月、本年、上年等日期,时间比较计算
https://blog.csdn.net/qq_15058285/article/details/119925056
- u-swipe-action 宽度计算的延迟导致组件加载时内部样式错误
https://toscode.gitee.com/umicro/uView/issues/I1Y50J 左图为电脑显示效果,右图为app显示效果. 原因:u-swipe-action 宽度计算的延迟 ...
- 启动vue项目失败,报错Failed at the node-sass@4.14.1 postinstall script.
https://www.cnblogs.com/xiaodangshan/p/13061618.html
- vue双向定位导航效果
需求:实现双向定位导航效果,点击左侧菜单,右侧滚动到相应的位置.滚动右边,左侧相应菜单高亮. html代码: 1 <ul class="EntTake_main_left" ...
- socket TCP DPT 网络编程
复习: ARP协议: 广播和单播 通过ip地址获得mac地址 机器A发起一个arp请求(只包含A的ip地址) 交换机接收到请求,广播这条消息 所有的机器都会接受到这条请求,只有需要寻找的机器B的ip地 ...
- CF1656F Parametric MST 题解
为了便于解题,先对 \(a\) 数组从小到大进行排序. 首先,根据定义可以得出总价值的表达式: \[\begin{aligned} W&=\sum\limits_{(u,v)\in E}[a_ ...
- 针对docker中的mongo容器增加鉴权
1. 背景 业务方的服务器经安全检查,发现以docker容器启动的mongo未增加鉴权的漏洞,随优化之 2. 配置 mongo以docker compose方式启动,镜像的版本号为4.2.6,dock ...
- 03-点亮LED灯
1.FPGA设计流程 1.设计规划 对项目需求了解,划分子功能模块,子功能模块的输入输出信号及通信关系 2.波形绘制 了解子模块的功能,画出框图,搞清楚如何通过输入信号得到输出信号,进而绘制波形图 3 ...
- [转帖]如何不耍流氓的做运维之-SHELL脚本
https://www.cnblogs.com/luoahong/articles/8504691.html 前言 大家都是文明人,尤其是做运维的,那叫一个斯文啊.怎么能耍流氓呢?赶紧看看,编写SHE ...
- [转帖]br 备份时排除某个库
https://tidb.net/book/tidb-monthly/2023/2023-02/usercase/excluded-a-storeroom-during-br-backup 生产环境中 ...