第 4 章 后端服务

现实中的服务不可能处于真空之中,大多数服务都需要与其他服务通信才能完成功能。

我们将这些支持性服务称为后端服务,接下来我们将通过创建一个新的服务并修改之前的团队服务与这个服务通信,以探索如何创建并消费后端服务。

微服务生态系统

后端服务是通过某种机制绑定到应用上的,而这种机制又可以由云设施(PaaS)管理。与打开一个文件不同,我们与泛化的存储服务通信。

资源绑定的概念其实是一种抽象,而具体的实现可能根据应用托管所在的云平台而有所差异。服务的绑定信息可能直接来自从平台注入的环境变量,或者来自外部的配置提供设施。

微服务是单一职责 SRP 和 里氏替换原则 LSP 的集中体现。对单个微服务的修改,不应该对任何其他服务产生任何影响。对服务内部模型的修改不应该破坏服务公开的 API 和外部模型。

开发位置服务

GitHub 链接:https://github.com/microservices-aspnetcore/locationservice.git

首先创建一个模型表示位置记录

public class LocationRecord {
public Guid ID { get; set; }
public float Latitude { get; set; }
public float Longitude { get; set; }
public float Altitude { get; set; }
public long Timestamp { get; set; }
public Guid MemberID { get; set; }
}

接下来,我们需要一个接口来表示位置信息仓储的契约

public interface ILocationRecordRepository {
LocationRecord Add(LocationRecord locationRecord);
LocationRecord Update(LocationRecord locationRecord);
LocationRecord Get(Guid memberId, Guid recordId);
LocationRecord Delete(Guid memberId, Guid recordId);
LocationRecord GetLatestForMember(Guid memberId);
ICollection<LocationRecord> AllForMember(Guid memberId);
}

控制器通过构造器注入的方式接收一个 ILocationRecordRepository 实例

[Route("locations/{memberId}")]
public class LocationRecordController : Controller { private ILocationRecordRepository locationRepository; public LocationRecordController(ILocationRecordRepository repository) {
this.locationRepository = repository;
} [HttpPost]
public IActionResult AddLocation(Guid memberId, [FromBody]LocationRecord locationRecord) {
locationRepository.Add(locationRecord);
return this.Created($"/locations/{memberId}/{locationRecord.ID}", locationRecord);
} [HttpGet]
public IActionResult GetLocationsForMember(Guid memberId) {
return this.Ok(locationRepository.AllForMember(memberId));
} [HttpGet("latest")]
public IActionResult GetLatestForMember(Guid memberId) {
return this.Ok(locationRepository.GetLatestForMember(memberId));
}
}

要让依赖注入能够提供仓储,只需要在启动期间把它添加为具有特定生命周期的服务,在 Stattup.cs 中

public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ILocationRecordRepository, InMemoryLocationRecordRepository>();
services.AddMvc();
}

优化团队服务

我们希望在查询特定团队成员的详细信息时,要包含他们最新的位置以及签入时间。实现这一功能,有两个主要步骤:

  • 将位置服务的 URL 绑定到团队的服务
  • 使用 URL 消费位置服务

使用环境变量配置服务的 URL

这个过程中要记住最重要的一点就是这些信息必须来自运行环境,而不是签入的代码。

消费 RESTful 服务

由于需要对团队服务终端控制器方法进行单元测试,并且在测试过程中不发出 HTTP 请求,我们要先为位置服务的客户端创建接口

将 teamservice 的分支切换为 location

public interface ILocationClient
{
Task<LocationRecord> GetLatestForMember(Guid memberId);
}

位置服务客户端的实现

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using StatlerWaldorfCorp.TeamService.Models;
using Newtonsoft.Json;
using Microsoft.Extensions.Configuration;
using System.Text; namespace StatlerWaldorfCorp.TeamService.LocationClient
{
public class HttpLocationClient : ILocationClient
{
public String URL { get; set; } public HttpLocationClient(string url)
{
this.URL = url;
} public async Task<LocationRecord> GetLatestForMember(Guid memberId)
{
LocationRecord locationRecord = null; using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(this.URL);
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); HttpResponseMessage response = await httpClient.GetAsync(String.Format("/locations/{0}/latest", memberId)); if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync();
locationRecord = JsonConvert.DeserializeObject<LocationRecord>(json);
}
} return locationRecord;
} public async Task<LocationRecord> AddLocation(Guid memberId, LocationRecord locationRecord)
{
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(this.URL);
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var jsonString = JsonConvert.SerializeObject(locationRecord);
var uri = String.Format("/locations/{0}", memberId);
HttpResponseMessage response =
await httpClient.PostAsync(uri, new StringContent(jsonString, Encoding.UTF8, "application/json")); if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync();
}
} return locationRecord;
}
}
}

接着,修改控制器 MembersController,调用位置服务客户端,将团队成员的最新位置添加到响应中

[HttpGet]
[Route("/teams/{teamId}/[controller]/{memberId}")]
public async virtual Task<IActionResult> GetMember(Guid teamID, Guid memberId)
{
Team team = repository.Get(teamID); if(team == null) {
return this.NotFound();
} else {
var q = team.Members.Where(m => m.ID == memberId); if(q.Count() < 1) {
return this.NotFound();
} else {
Member member = (Member)q.First(); return this.Ok(new LocatedMember {
ID = member.ID,
FirstName = member.FirstName,
LastName = member.LastName,
LastLocation = await this.locationClient.GetLatestForMember(member.ID)
});
}
}
}

我们用的 LocationRecord 模型类是团队服务所私有的。

团队服务和位置服务并不共用模型,团队服务一直只依赖于位置服务公共的 API, 而不依赖于内部实现。

接下来我们希望增加一种能力,为使用应用的每个人维护签到过的历史位置信息,创建一个位置服务用于单独管理位置数据,它公开一个方便的端点来检索团队成员的最新位置。

  • 团队服务:microservices-aspnetcore/teamservice:location
  • 位置服务:microservices-aspnetcore/locationservice:nodb

首先启动团队服务

$ docker run -p 5000:5000 -e PORT=5000 \
-e LOCATION__URL=http://localhost:5001 \
dotnetcoreservices/teamservice:location

这样就能在 5000 端口启动团队服务,把容器内的 5000 端口映射到 localhost 的 5000 端口,并让团队服务从 http://localhost:5001 调用位置服务

团队服务启动完成后再启动位置服务

$ docker run -p 5001:5001 -e PORT=5001 \
dotnetcoreservices/locationservice:nodb

两个服务启动完成后,可通过 docker ps 命令查看各个服务的 Docker 配置。

接下来,运行一系列命令确保一切工作正常。

创建一个新的团队

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

通过向 /teams/{id}/members 资源发送 POST 请求添加新的成员

$ curl -H "Content-Type:application/json" -X POST -d \
'{"id":"63e7acf8-8fae-42ec-9349-3c8593ac8292", \
"firstName":"Al", \
"lastName":"Foo"}' \
http://localhost:5000/teams/e52baa63-d511-417e-9e54-7aab04286281/members

尝试查询团队详情,确保一切工作正常

$ curl http://localhost:5000/teams/e52baa63-d511-417e-9e54-7aab04286281

往位置服务添加新的位置信息

$ curl -H "Content-Type:application/json" -X POST -d \
'{"id":"64c3e69f-1580-4b2f-a9ff-2c5f3b8f0elf", \
"latitude":12.0,"longitude":12.0,"altitude":10.0, \
"timestamp":0, \
"memberId":"63e7acf8-8fae-42ec-9349-3c8593ac8292"}' \
http://localhost:5001/locations/63e7acf8-8fae-42ec-9349-3c8593ac8292

现在可以真正测试团队服务和位置服务之间的集成了,从 teams/{id}/members/{id} 资源查询团队成员的详细信息

$ curl http://localhost:5000/teams/e52baa63-d511-417e-9e54-7aab04286281 \
/members/63e7acf8-8fae-42ec-9349-3c8593ac8292

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。

《ASP.NET Core 微服务实战》-- 读书笔记(第4章)的更多相关文章

  1. JavaScript DOM编程艺术读书笔记(三)

    第七章 动态创建标记 在web浏览器中往文档添加标记,先回顾下过去使用的技术: <body> <script type="text/javascript"> ...

  2. JavaScript DOM编程艺术读书笔记(二)

    第五章 最佳实践 平稳退化(graceful degradation):如果正确使用了JavaScript脚本,可以让访问者在他们的浏览器不支持JavaScript的情况下仍能顺利地浏览你网站.虽然某 ...

  3. JavaScript DOM编程艺术读书笔记(一)

    第一章,第二章 DOM:是一套对文档的内容进行抽象和概念化的方法. W3C中的定义:一个与系统平台和编程语言无关的接口,程序和脚本可以通过这个接口动态的访问和修改文档的内容,结构和样式. DHTML( ...

  4. JavaScript DOM编程艺术 - 读书笔记1-3章

    1.JavaScript语法 准备工作 一个普通的文本编辑器,一个Web浏览器. JavaScript代码必须通过Html文档才能执行,第一种方式是将JavaScript代码放到文档<head& ...

  5. JavaScript DOM编程艺术 读书笔记

    2. JavaScript语法 2.1 注释      HTML允许使用"<!--"注释跨越多个行,但JavaScript要求这种注释的每行都必须在开头加上"< ...

  6. JavaScript DOM编程艺术读书笔记(四)

    第十章 实现动画效果 var repeat = "moveElement('"+elementID+"',"+final_x+","+fin ...

  7. JavaScript DOM编程艺术-学习笔记(第二章)

    1.好习惯从末尾加分号:开始 2.js区分大小写 3.程序界万能的命名法则:①不以,数字开头的数字.字母.下划线.美元符号 ②提倡以下划线命名法来命名变量,以驼峰命名法来命名函数.但是到了公司往往会身 ...

  8. 《javascript dom编程艺术》笔记(一)——优雅降级、向后兼容、多个函数绑定onload函数

    刚刚开始自学前端,如果不对请指正:欢迎各位技术大牛指点. 开始学习<javascript dom编程艺术>,整理一下学习到的知识.今天刚刚看到第六章,记下get到的几个知识点. 优雅降级 ...

  9. JavaScript DOM编程艺术学习笔记(一)

    嗯,经过了一周的时间,今天终于将<JavaScript DOM编程艺术(第2版)>这本书看完了,感觉受益匪浅,我和作者及出版社等等都不认识,无意为他们做广告,不过本书确实值得一看,也值得推 ...

  10. JavaScript DOM编程艺术-学习笔记

    发现基础不是很好,补习一下.37买了2本书(dom编程和高级程序设计). 以前读书总是自己勾勾画画,有点没意思.现在写下来,说不定会成为传世经典.哈哈...........随便扯扯淡. 第一天(201 ...

随机推荐

  1. vue.draggable中文文档

    http://www.itxst.com/vue-draggable/fiamvqam.html https://blog.csdn.net/a772116804/article/details/10 ...

  2. Ubuntu22.04上使用systemctl命令配置开机自启动服务、脚本、命令

    systemctl介绍 systemctl是Linux系统上用于管理系统服务(systemd单元)的命令行工具.它可以用来启动.停止.重启.禁用.启用.查询和管理系统服务的状态. 以下是一些常用的sy ...

  3. C++ 覆盖写文件

    写文件有三种模式: 截断写,文件打开之后立即清空原有内容 附加写,文件打开之后不清空原有内容,每次只能在文件最后写入 覆盖写,文件打开之后不清空原有内容,可以在文件任意位置写入 例如:文件原有内容为 ...

  4. 01-UPF介绍

    Mainstream Low Power techniques clock gating - 低功耗的设计风格,4bit以上可以通过综合工具增加ICG APR的时候可以更换器件VT Advanced ...

  5. 【MCU】浮点数如何判等

    [来源]https://mp.weixin.qq.com/s/481H4imm73IIS1yFI7-DNA

  6. [转帖]【linux命令学习】— sar 命令学习

    https://blog.csdn.net/u013332124/article/details/101075521 一.命令使用介绍 sar命令全称 System Activity Report,它 ...

  7. [转帖]Split Region 使用文档

    https://docs.pingcap.com/zh/tidb/stable/sql-statement-split-region 在 TiDB 中新建一个表后,默认会单独切分出 1 个 Regio ...

  8. [转帖]15分钟了解TiDB

    https://zhuanlan.zhihu.com/p/338947811 由于目前的项目把mysql换成了TiDb,所以特意来了解下tidb.其实也不能说换,由于tidb和mysql几乎完全兼容, ...

  9. [转帖]kingbase(人大金仓)的一些常用表操作语句

    包括 1)创建表 2)删除表 3)加字段 4)字段换名 5)字段改类型 6)字段添加注释 7)修改字段为自增类型 8)增加主键 9)查看模式下的表 一.创建和删除表 DROP TABLE IF EXI ...

  10. linux机制

    cpu Cache 工作原理:文中对Cache的一致性提出了两种策略:基于监听的和基于目录的.前者是所有Cache均监听各个Cache的写操作,当一个Cache中的数据被写了,其处理方式有:写更新协议 ...