第 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. tinymce富文本编辑器升级问题

    突然这样,之前好好地.

  2. BOM概述

  3. paddlespeech on centos7

    概述 paddlespeech是百度飞桨平台的开源工具包,主要用于语音和音频的分析处理,其中包含多个可选模型,提供语音识别.语音合成.说话人验证.关键词识别.音频分类和语音翻译等功能. paddles ...

  4. ORACLE Enterprise Manager Database Express(OEM-express)(遇到localhost拒绝访问情况)配置端口和启动方法

    1.问题 之前一直进不去ORACLE Enterprise Manager Database Express,显示的是localhost拒绝了访问,经过查阅知道是没有配置相应端口. 2.解决方法 转载 ...

  5. [转帖]MySQL 官方出品,比 mydumper 更快的多线程逻辑备份工具-MySQL Shell Dump & Load

    MySQL 官方出品,比 mydumper 更快的多线程逻辑备份工具-MySQL Shell Dump & Load - 知乎 (zhihu.com) ​ 目录 收起 什么是 MySQL Sh ...

  6. [转帖]DBWR与LGWR的写入机制

    https://www.jianshu.com/p/6c87cb6cd320 读与写是每个数据库提供的最基本的功能.当数据库中出现第一个进程时,总免不了要将数据从磁盘上加载到内存中,一次数据库的物理I ...

  7. [转帖]ssd/san/sas/磁盘/光纤/RAID性能比较

    https://plantegg.github.io/2022/01/25/ssd_san%E5%92%8Csas%E7%A3%81%E7%9B%98%E6%80%A7%E8%83%BD%E6%AF% ...

  8. nginx 进行目录浏览的简单配置

    1. 公司网络安全不让用vsftpd的匿名网络访问了, 没办法 只能够使用 nginx 通过http协议来处理. 2. 最简单的办法就是另外开一个nginx进程简单设置一下nginx的配置文件 wor ...

  9. Springboot actuator的简单使用

    Springboot actuator的简单使用 简介 公司基于springboot研发的系统,开发已经默认集成了actuator 为了安全起见这个插件模式是不开启的. 今天与研发同事进行了沟通,简单 ...

  10. 让你轻松看懂defer和async

    defer和async产生的原因 HTML 网页中,浏览器通过<script>标签加载 JavaScript 脚本. <!-- 页面内嵌的脚本 --> <script t ...