第 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. 大数相加 a+b

    #include<stdio.h> #include<string.h> #include<stdlib.h> #include<ctype.h> #i ...

  2. Oracle建立索引前后性能比较

    1.SQL语句 create table C##HR.t_noindex as select * from all_objects; create table C##HR.t_indexed as s ...

  3. [转帖]解决Java/MySQL性能问题的思路

    https://plantegg.github.io/2023/08/28/%E8%A7%A3%E5%86%B3%E9%97%AE%E9%A2%98%E6%80%9D%E8%B7%AF/ 10年前写的 ...

  4. Python学习之十八_django的学习(二)

    Python学习之十八_django的学习(二) 前言 前面学习了基本的django的使用. 这里想着稍微深入一点学习templates 以及进行级联的路由展示. 修改配置文件 要想使用 templa ...

  5. [转帖]华为OpenEuler欧拉系统添加epel源方法

    https://blog.whsir.com/post-7002.html   由于国产华为OpenEuler欧拉系统的版本命名是22.03.22.03这种,并且在查看版本的路径中是/etc/open ...

  6. redis 设置密码之后,通过命令行一键刷新的办法

    之前以为很麻烦 发现还是自己太low了. redis-cli -a Test1127 flushall

  7. 浪潮的CS5260F CS5260H CS5260Z

    助力国产操作系统新生态!麒麟信安与东方通.浪潮.新华三.长城超云等多家生态伙伴完成产品兼容性认证 作者:湖南麒麟信安科技股份有限公司时间:2022-03-11 16:28:11 我要发布 关键词: 国 ...

  8. Codeforces round 919 (div2)

    Problem - A - Codeforces 暴力枚举 就可以: #include <bits/stdc++.h> #define int long long using namesp ...

  9. 【图论,网络流】CF1525F Goblins And Gnomes

    Problem Link 你在打怪.你有一个 \(n\) 个点 \(m\) 条边的 DAG,接下来会有 \(k\) 波怪来袭,第 \(i\) 波怪有 \(i\) 个,它们会各自选择走一条路径,要求它们 ...

  10. ClickHouse(06)ClickHouse建表语句DDL详细解析

    目录 当前服务器上创建表(单节点) 语法形式 使用显式架构 从相同结构的表复制创建 从表函数创建 从选择查询创建 分布式集群创建表 临时表 分区表 创建表语句关键字解析 空值或非空修饰符 默认值表达式 ...