目录

介绍

命名协议

MicroCommerce应用程序结构

MicroCommerce应用开发

1.接口项目,微服务接口和模型类

2. ProductCatalog项目

3. ShoppingCart项目

4. ActivityLogger项目

5. WebUI项目,用户界面

6.关于通用功能的几句话

应用测试

结论

缺点


  • 下载源代码26.3 KB

介绍

几乎所有在.NET Core中使用微服务的人都可能知道Christian Horsdal的书“.NET Core中的微服务:Nancy中的示例”。在这里很好地描述了基于微服务构建应用程序的方法,包括监视、记录和访问控制进行了详细讨论。唯一缺少的是使微服务之间的交互自动化的工具。

在通常的方法中,开发微服务时,将并行开发该微服务的Web客户端。并且每次微服务的Web界面更改时,都必须为Web客户端中的相应更改花费更多的精力。使用OpenNET生成一对web-api/web-client的想法也很费力,我希望对开发人员来说更透明。

因此,采用一种替代方法来开发应用程序,我想:

  • .NET接口使用属性来描述微服务结构,这些属性描述了方法的类型、路由和传递参数的方式,就像在MVC中所做的那样。
  • 微服务功能是在.NET类中专门开发的,实现了此接口。微服务端点的发布应该是自动的,不需要复杂的设置。
  • 微服务的Web客户端应基于该接口自动生成,并通过IoC容器提供。
  • 应该有一种机制来组织从与用户界面交互的主应用程序到微服务端点的重定向。

根据这些标准,开发了软件包Nuget Shed.CoreKit.WebApi。除此之外,还创建了辅助程序包Shed.CoreKit.WebApi.Abstractions,其中包含可用于开发不需要主程序包功能的通用装配项目的属性和类。

下面,我们将在前面提到的克里斯蒂安·霍斯达尔(Christian Horsdal)所描述的MicroCommerce应用程序的开发中使用这些包的功能。

命名协议

在下文中,我们使用以下术语:

  • 微服务是一个ASP.NET Core应用程序(项目),可以通过IIS或在Docker容器中基于控制台运行。
  • 接口是一个.NET实体,是一组没有实现的方法和属性。
  • 端点是微服务应用程序或接口实现的根的路径。例如:http://localhost:5001, http://localhost:5000/products
  • 路由是从端点到接口方法的路径。默认情况下,可以使用与MVC中相同的方式定义它,也可以使用属性进行设置。

MicroCommerce应用程序结构

  • ProductCatalog 是提供产品信息的微服务。
  • ShoppingCart是一种可提供有关用户购买的信息以及添加/删除购买的功能的微服务。当用户的购物篮状态更改时,将生成事件以通知其他微服务。
  • ActivityLogger是一种微服务,用于收集有关其他微服务的事件的信息。提供用于接收日志的端点。
  • WebUI 是应用程序的一个用户界面,应该将其实现为单个页面应该程序。
  • Interfaces——微服务接口和模型类
  • Middleware——所有微服务的通用功能

MicroCommerce应用开发

创建一个空的.NET Core解决方案。将WebUI项目添加为空的ASP.NET Core WebApplication。接下来,添加ProductCatalog,ShoppingCart,ActivityLog微服务项目,以及空ASP.NET Core WebApplication项目。最后,我们添加两个类库——接口和中间件。

1.接口项目,微服务接口和模型类

安装Shed.CoreKit.WebApi.Abstractions nuget软件包。

创建IProductCatalog接口和模型:

//
// Interfaces/IProductCatalog.cs
//using MicroCommerce.Models;
using Shed.CoreKit.WebApi;
using System;
using System.Collections.Generic;namespace MicroCommerce
{public interface IProductCatalog{IEnumerable<Product> Get();[Route("get/{productId}")]public Product Get(Guid productId);}
}
//
// Interfaces/Models/Product.cs
//using System;namespace MicroCommerce.Models
{public class Product{public Guid Id { get; set; }public string Name { get; set; }public Product Clone(){return new Product{Id = Id,Name = Name};}}
}

使用该Route属性与ASP.NET Core MVC中的属性没有什么不同,但是请记住,此属性必须来自命名空间 Shed.CoreKit.WebApi,并且不能来自其他。这同样适用于HttpGet、HttpPut、HttpPost、HttpPatch、HttpDelet和FromBody属性,如果使用的话。

使用Http [Methodname]类型的属性的规则与MVC中的规则相同,即,如果接口方法名称的前缀与所需Http方法的名称匹配,则无需额外定义它,否则我们将使用相应的属性。

如果要从请求正文中检索此参数,则将该FromBody属性应用于方法参数。我注意到,像ASP.NET Core MVC一样,必须始终指定它,没有默认规则。在方法参数中,只有一个具有此属性的参数。

创建IShoppingCart 接口和模型:

//
// Interfaces/IShoppingCart.cs
//using MicroCommerce.Models;
using Shed.CoreKit.WebApi;
using System;
using System.Collections.Generic;namespace MicroCommerce
{public interface IShoppingCart{Cart Get();[HttpPut, Route("addorder/{productId}/{qty}")]Cart AddOrder(Guid productId, int qty);Cart DeleteOrder(Guid orderId);[Route("getevents/{timestamp}")]IEnumerable<CartEvent> GetCartEvents(long timestamp);}
}
//
// Interfaces/IProductCatalog/Order.cs
//using System;namespace MicroCommerce.Models
{public class Order{public Guid Id { get; set; }public Product Product { get; set; }public int Quantity { get; set; }public Order Clone(){return new Order{Id = Id,Product = Product.Clone(),Quantity = Quantity};}}
}
//
// Interfaces/Models/Cart.cs
//using System;namespace MicroCommerce.Models
{public class Cart{public IEnumerable<Order> Orders { get; set; }}
}
//
// Interfaces/Models/CartEvent.cs
//using System;namespace MicroCommerce.Models
{public class CartEvent: EventBase{public CartEventTypeEnum Type { get; set; }public Order Order { get; set; }}
}
//
// Interfaces/Models/CartEventTypeEnum.cs
//using System;namespace MicroCommerce.Models
{public enum CartEventTypeEnum{OrderAdded,OrderChanged,OrderRemoved}
}
//
// Interfaces/Models/EventBase.cs
//using System;namespace MicroCommerce.Models
{public abstract class EventBase{private static long TimestampBase;static EventBase(){TimestampBase = new DateTime(2000, 1, 1).Ticks;}public long Timestamp { get; set; }public DateTime Time { get; set; }public EventBase(){Time = DateTime.Now;Timestamp = Time.Ticks - TimestampBase;}}
}

关于事件EventBase的基本类型的几句话。发布事件时,我们使用本书中介绍的方法,即,任何事件都包含一个Timestamp,当轮询事件源时,侦听器将发送最后收到的时间戳。不幸的是,对于大的值,long类型被错误地转换为Number JavaScript类型,因此我们使用了一些技巧,我们减去了基准日期(Timestamp = Time.Ticks - TimestampBase)的时间戳。基本日期的具体值绝对不重要。

创建IActivityLogger接口和模型:

//
// Interfaces/IActivityLogger.cs
//using MicroCommerce.Models;
using System.Collections.Generic;namespace MicroCommerce
{public interface IActivityLogger{IEnumerable<LogEvent> Get(long timestamp);}
}
//
// Interfaces/Models/LogEvent.cs
//namespace MicroCommerce.Models
{public class LogEvent: EventBase{public string Description { get; set; }}
}

2. ProductCatalog项目

打开Properties/launchSettings.json,将项目绑定到端口5001。

//
// Properties/launchSettings.json
//{"iisSettings": {"windowsAuthentication": false,"anonymousAuthentication": true,"iisExpress": {"applicationUrl": "http://localhost:60670","sslPort": 0}},"profiles": {"MicroCommerce.ProductCatalog": {"commandName": "Project","environmentVariables": {"ASPNETCORE_ENVIRONMENT": "Development"},"applicationUrl": "http://localhost:5001"}}
}

将Shed.CoreKit.WebApi nuget软件包安装到项目中,并添加到Interfaces和Middleware 项目的链接。Middleware项目将在下面更详细地描述。

创建IProductCatalog接口实现:

//
// ProductCatalog/ProductCatalog.cs
//using MicroCommerce.Models;
using System;
using System.Collections.Generic;
using System.Linq;namespace MicroCommerce.ProductCatalog
{public class ProductCatalogImpl : IProductCatalog{private Product[] _products = new[]{new Product{ Id = new Guid("6BF3A1CE-1239-4528-8924-A56FF6527595"), Name = "T-shirt" },new Product{ Id = new Guid("6BF3A1CE-1239-4528-8924-A56FF6527596"), Name = "Hoodie" },new Product{ Id = new Guid("6BF3A1CE-1239-4528-8924-A56FF6527597"), Name = "Trousers" }};public IEnumerable<Product> Get(){return _products;}public Product Get(Guid productId){return _products.FirstOrDefault(p => p.Id == productId);}}
}

product目录被存储在静态字段中,以简化示例。当然,在实际的应用程序中,您需要使用一些其他存储,可以通过依赖注入将其作为依赖提供。

现在,此实现需要作为端点进行连接。如果使用传统方法,则必须使用MVC基础结构,即创建一个控制器,将我们的实现作为依赖项传递给它,配置路由等。使用Shed.CoreKit.WebApi Nuget包会使此过程变得更加容易。在Dependency Injection中注册我们的实现,然后使用Shed.CoreKit.WebApi包中的UseWebApiEndpoint扩展方法将其声明为端点就足够了。我们在安装程序中执行此操作:

//
// ProductCatalog/Setup.cs
//using MicroCommerce.Middleware;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Shed.CoreKit.WebApi;namespace MicroCommerce.ProductCatalog
{public class Startup{public void ConfigureServices(IServiceCollection services){services.AddCorrelationToken();services.AddCors();// register the implementation as dependencyservices.AddTransient<IProductCatalog, ProductCatalogImpl>();services.AddLogging(builder => builder.AddConsole());services.AddRequestLogging();}public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseCorrelationToken();app.UseRequestLogging();app.UseCors(builder =>{builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();});// bind the registered implementation to the endpointapp.UseWebApiEndpoint<IProductCatalog>();}}
}

这导致以下事实:方法出现在微服务中:

http://localhost: 5001/get
http://localhost: 5001/get/<productid>

UseWebApiEndpoint方法可以接受可选的根参数。

如果我们通过以下方式连接端点:app.UseWebApiEndpoint<IProductCatalog>(“products”),则微服务端点将如下所示:

http://localhost:5001/products/get

如果需要将多个接口连接至微服务,这将很有用。

这就是您需要做的。您可以启动微服务并测试其方法。

Setup中的其余代码将配置并启用其他功能。

一对services.AddCors()/app.UseCors(...)允许在项目中使用跨域请求。从用户界面重定向请求时,这是必需的。

如克里斯汀·霍斯达尔(Christian Horsdal)所述,一对services.AddCorrelationToken()/app.UseCorrelationToken()允许在记录请求时使用相关令牌。我们将在后面讨论。

最后,一对services.AddRequestLogging()/app.UseRequestLogging()启用来自中间件项目的请求日志记录。我们也将在稍后返回。

3. ShoppingCart项目

以与ProductCatalog相同的方式将项目绑定到端口5002 。

将Shed.CoreKit.WebApi nuget软件包以及指向Interfaces和Middleware项目的链接添加到项目中。

创建IShoppingCart接口实现。

//
// ShoppingCart/ShoppingCart.cs
//using MicroCommerce.Models;
using System;
using System.Collections.Generic;
using System.Linq;namespace MicroCommerce.ShoppingCart
{public class ShoppingCartImpl : IShoppingCart{private static List<Order> _orders = new List<Order>();private static List<CartEvent> _events = new List<CartEvent>();private IProductCatalog _catalog;public ShoppingCartImpl(IProductCatalog catalog){_catalog = catalog;}public Cart AddOrder(Guid productId, int qty){var order = _orders.FirstOrDefault(i => i.Product.Id == productId);if(order != null){order.Quantity += qty;CreateEvent(CartEventTypeEnum.OrderChanged, order);}else{var product = _catalog.Get(productId);if (product != null){order = new Order{Id = Guid.NewGuid(),Product = product,Quantity = qty};_orders.Add(order);CreateEvent(CartEventTypeEnum.OrderAdded, order);}}return Get();}public Cart DeleteOrder(Guid orderId){var order = _orders.FirstOrDefault(i => i.Id == orderId);if(order != null){_orders.Remove(order);CreateEvent(CartEventTypeEnum.OrderRemoved, order);}return Get();}public Cart Get(){return new Cart{Orders = _orders};}public IEnumerable<CartEvent> GetCartEvents(long timestamp){return _events.Where(e => e.Timestamp > timestamp);}private void CreateEvent(CartEventTypeEnum type, Order order){_events.Add(new CartEvent{Timestamp = DateTime.Now.Ticks,Time = DateTime.Now,Order = order.Clone(),Type = type});}}
}

在这里,与ProductCatalog中的一样,我们使用static字段作为存储。但是这个微服务仍然使用调用ProductCatalog来获取有关产品的信息,因此我们将IProductCatalog的链接作为依赖项传递给构造函数。

现在,需要在DI中定义此依赖关系,为此,我们使用Shed.CoreKit.WebApi包中的AddWebApiEndpoints扩展方法。此方法将WebApi客户端生成器注册为IoC容器中IProductCatalog接口的工厂方法。

生成WebApi客户端时,工厂使用依赖项System.Net.Http.HttpClient。如果应用程序需要对HttpClient进行一些特殊设置(凭证、特殊标头/令牌等),则应在IoC容器中注册HttpClient时执行此操作。

//
// ShoppingCart/Settings.cs
//using MicroCommerce.Middleware;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Shed.CoreKit.WebApi;
using System.Net.Http;namespace MicroCommerce.ShoppingCart
{public class Startup{public void ConfigureServices(IServiceCollection services){services.AddCorrelationToken();services.AddCors();services.AddTransient<IShoppingCart, ShoppingCartImpl>();services.AddTransient<HttpClient>();// register a dependency binded to the endpointservices.AddWebApiEndpoints(new WebApiEndpoint<IProductCatalog>(new System.Uri("http://localhost:5001")));services.AddLogging(builder => builder.AddConsole());services.AddRequestLogging();}public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseCorrelationToken();app.UseRequestLogging("getevents");app.UseCors(builder =>{builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();});app.UseWebApiEndpoint<IShoppingCart>();}}
}

AddWebApiEndpoints方法可以采用任意数量的参数,因此可以通过一次调用此方法来配置所有依赖项。

否则,所有设置与ProductCatalog相似。

4. ActivityLogger项目

以与ProductCatalog相同的方式将项目绑定到端口5003 。

将Shed.CoreKit.WebApi nuget软件包安装到项目中,并添加链接到Interfaces和Middleware项目中。

创建IActivityLogger接口实现。

//
// ActivityLogger/ActivityLogger.cs
//using MicroCommerce;
using MicroCommerce.Models;
using System.Collections.Generic;
using System.Linq;namespace MicroCommerce.ActivityLogger
{public class ActivityLoggerImpl : IActivityLogger{private IShoppingCart _shoppingCart;private static long timestamp;private static List<LogEvent> _log = new List<LogEvent>();public ActivityLoggerImpl(IShoppingCart shoppingCart){_shoppingCart = shoppingCart;}public IEnumerable<LogEvent> Get(long timestamp){return _log.Where(i => i.Timestamp > timestamp);}public void ReceiveEvents(){var cartEvents = _shoppingCart.GetCartEvents(timestamp);if(cartEvents.Count() > 0){timestamp = cartEvents.Max(c => c.Timestamp);_log.AddRange(cartEvents.Select(e => new LogEvent{Description = $"{GetEventDesc(e.Type)}: '{e.Order.Product.Name} ({e.Order.Quantity})'"}));}}private string GetEventDesc(CartEventTypeEnum type){switch (type){case CartEventTypeEnum.OrderAdded: return "order added";case CartEventTypeEnum.OrderChanged: return "order changed";case CartEventTypeEnum.OrderRemoved: return "order removed";default: return "unknown operation";}}}
}

它还使用了对另一个微服务(IShoppingCart)的依赖。但是此服务的任务之一是侦听其他服务的事件,因此我们添加了另一个ReceiveEvents() 方法,将从调度程序中调用该方法。我们将其另外添加到项目中。

//
// ActivityLogger/Scheduler.cs
//using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;namespace MicroCommerce.ActivityLogger
{public class Scheduler : BackgroundService{private IServiceProvider ServiceProvider;public Scheduler(IServiceProvider serviceProvider){ServiceProvider = serviceProvider;}protected override Task ExecuteAsync(CancellationToken stoppingToken){Timer timer = new Timer(new TimerCallback(PollEvents), stoppingToken, 2000, 2000);return Task.CompletedTask;}private void PollEvents(object state){try{var logger = ServiceProvider.GetService(typeof(MicroCommerce.IActivityLogger)) as ActivityLoggerImpl;logger.ReceiveEvents();}catch{}}}
}

项目设置与上一段相似。
另外,我们只需要添加以前开发的调度程序。

//
// ActivityLogger/Setup.cs
//using System.Net.Http;
using MicroCommerce;
using MicroCommerce.Middleware;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Shed.CoreKit.WebApi;namespace MicroCommerce.ActivityLogger
{public class Startup{public void ConfigureServices(IServiceCollection services){services.AddCorrelationToken();services.AddCors();services.AddTransient<IActivityLogger, ActivityLoggerImpl>();services.AddTransient<HttpClient>();services.AddWebApiEndpoints(new WebApiEndpoint<IShoppingCart>(new System.Uri("http://localhost:5002")));// register the schedulerservices.AddHostedService<Scheduler>();services.AddLogging(builder => builder.AddConsole());services.AddRequestLogging();}public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseCorrelationToken();app.UseRequestLogging("get");app.UseCors(builder =>{builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();});app.UseWebApiEndpoint<IActivityLogger>();}}
}

5. WebUI项目,用户界面

以与ProductCatalog相同的方式将项目绑定到端口5000。

将Shed.CoreKit.WebApi nuget软件包安装到项目中。只有当我们要使用对项目内部微服务的调用时,才需要到Interfaces和Middleware项目的链接。

实际上,这是一个普通的ASP.NET项目,我们可以在其中使用MVC,即与UI交互,我们可以创建将微服务接口用作依赖项的控制器。但是,将用户界面仅留在该项目之后,将所有调用从UI直接重定向到微服务,这是更有趣和实用的。为此,使用Shed.CoreKit.WebApi包中的UseWebApiRedirect扩展方法。

//
// WebUI/Setup.cs
//using MicroCommerce.Interfaces;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Shed.CoreKit.WebApi;
using System.Net.Http;namespace MicroCommerce.Web
{public class Startup{public void ConfigureServices(IServiceCollection services){}public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.Use(async (context, next) =>{//  when root calls, the start page will be returnedif(string.IsNullOrEmpty(context.Request.Path.Value.Trim('/'))){context.Request.Path = "/index.html";}await next();});app.UseStaticFiles();// add redirects to microservicesapp.UseWebApiRedirect("api/products", new WebApiEndpoint<IProductCatalog>(new System.Uri("http://localhost:5001")));app.UseWebApiRedirect("api/orders", new WebApiEndpoint<IShoppingCart>(new System.Uri("http://localhost:5002")));app.UseWebApiRedirect("api/logs", new WebApiEndpoint<IActivityLogger>(new System.Uri("http://localhost:5003")));}}
}

一切都非常简单。现在,例如,如果UI 发出了对“http://localhost:5000/api/products/get”的请求,它将自动重定向到“http://localhost:5001/get”。当然,为此,微服务必须允许跨域请求,但是我们早些时候已允许这样做(请参阅CORS在微服务的实现中)。

现在剩下的就是开发用户界面,“单页应用程序”最适合于此。您可以使用Angular或React,但是我们只是使用现成的bootstrap主题和kickoutjs框架来创建一个小页面。

<!DOCTYPE html>  <!-- WebUI/wwwroot/index.html -->
<html>
<head><meta charset="utf-8" /><title></title><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/4.5.0/materia/bootstrap.min.css" />"<style type="text/css">body {background-color: #0094ff;}.panel {background-color: #FFFFFF;margin-top:20px;padding:10px;border-radius: 4px;}.table .desc {vertical-align: middle;font-weight:bold;}.table .actions {text-align:right;white-space:nowrap;width:40px;}</style><script src="https://code.jquery.com/jquery-3.5.1.min.js"integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="crossorigin="anonymous"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.1/knockout-latest.min.js"></script><script src="../index.js"></script>
</head>
<body><div class="container"><div class="row"><div class="col-12"><div class="panel panel-heading"><div class="panel-heading"><h1>MicroCommerce</h1></div></div></div><div class="col-xs-12 col-md-6"><div class="panel panel-default"><h2>All products</h2><table class="table table-bordered" data-bind="foreach:products"><tr><td data-bind="text:name"></td><td class="actions"><a class="btn btn-primary" data-bind="click:function(){$parent.addorder(id, 1);}">ADD</a></td></tr></table></div></div><div class="col-xs-12 col-md-6"><div class="panel panel-default" data-bind="visible:shoppingCart()"><h2>Shopping cart</h2><table class="table table-bordered" data-bind="foreach:shoppingCart().orders"><tr><td data-bind="text:product.name"></td><td class="actions" data-bind="text:quantity"></td><td class="actions"><a class="btn btn-primary" data-bind="click:function(){$parent.delorder(id);}">DELETE</a></td></tr></table></div></div><div class="col-12"><div class="panel panel-default"><h2>Operations history</h2><!-- ko foreach:logs --><div class="log-item"><span data-bind="text:time"></span><span data-bind="text:description"></span></div><!-- /ko --></div></div></div></div><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script><script>var model = new IndexModel();ko.applyBindings(model);</script>
</body>
</html>
//
// WebUI/wwwroot/index.js
//function request(url, method, data) {return $.ajax({cache: false,dataType: 'json',url: url,data: data ? JSON.stringify(data) : null,method: method,contentType: 'application/json'});
}function IndexModel() {this.products = ko.observableArray([]);this.shoppingCart = ko.observableArray(null);this.logs = ko.observableArray([]);var _this = this;this.getproducts = function () {request('/api/products/get', 'GET').done(function (products) {_this.products(products);console.log("get products: ", products);}).fail(function (err) {console.log("get products error: ", err);});};this.getcart = function () {request('/api/orders/get', 'GET').done(function (cart) {_this.shoppingCart(cart);console.log("get cart: ", cart);}).fail(function (err) {console.log("get cart error: ", err);});};this.addorder = function (id, qty) {request(`/api/orders/addorder/${id}/${qty}`, 'PUT').done(function (cart) {_this.shoppingCart(cart);console.log("add order: ", cart);}).fail(function (err) {console.log("add order error: ", err);});};this.delorder = function (id) {request(`/api/orders/deleteorder?orderId=${id}`, 'DELETE').done(function (cart) {_this.shoppingCart(cart);console.log("del order: ", cart);}).fail(function (err) {console.log("del order error: ", err);});};this.timestamp = Number(0);this.updateLogsInProgress = false;this.updatelogs = function () {if (_this.updateLogsInProgress)return;_this.updateLogsInProgress = true;request(`/api/logs/get?timestamp=${_this.timestamp}`, 'GET').done(function (logs) {if (!logs.length) {return;}ko.utils.arrayForEach(logs, function (item) {_this.logs.push(item);_this.timestamp = Math.max(_this.timestamp, Number(item.timestamp));});console.log("update logs: ", logs, _this.timestamp);}).fail(function (err) {console.log("update logs error: ", err);}).always(function () { _this.updateLogsInProgress = false; });};this.getproducts();this.getcart();this.updatelogs();setInterval(() => _this.updatelogs(), 1000);
}

我不会详细解释UI的实现,因为这超出了本文主题的范围,我只想说JavaScript模型定义了用于绑定HTML标记的属性和集合以及响应于HTML标记的函数单击按钮可访问WebApi重定向到相应微服务的端点。用户界面的外观及其工作方式,我们将在后面的“测试应用程序”部分中讨论。

6.关于通用功能的几句话

在本文中,我们没有涉及应用程序开发的其他方面,例如日志记录、运行状况监视、身份验证和授权。在克里斯蒂安·霍斯达尔(Christian Horsdahl)的书中都对此进行了详细考虑,并且在上述方法的框架内非常适用。但是,这些方面对于每个应用程序来说都是太具体了,将它们放在Nuget包中没有意义,最好只在应用程序中创建一个单独的程序集。我们已经创建了这样的程序集——这就是中间件。例如,仅在此处添加查询日志记录功能,我们在开发微服务时已将其链接(请参阅第2-4段)。

//
// Middleware/RequestLoggingExt.cs
//using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;namespace MicroCommerce.Middleware
{public static class RequestLoggingExt{private static RequestLoggingOptions Options = new RequestLoggingOptions();public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder builder, params string[] exclude){Options.Exclude = exclude;return builder.UseMiddleware<RequestLoggingMiddleware>();}public static IServiceCollection AddRequestLogging(this IServiceCollection services){return services.AddSingleton(Options);}}internal class RequestLoggingMiddleware{private readonly RequestDelegate _next;private readonly ILogger _logger;private RequestLoggingOptions _options;public RequestLoggingMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, RequestLoggingOptions options){_next = next;_options = options;_logger = loggerFactory.CreateLogger("LoggingMiddleware");}public async Task InvokeAsync(HttpContext context){if(_options.Exclude.Any(i => context.Request.Path.Value.Trim().ToLower().Contains(i))){await _next.Invoke(context);return;}var request = context.Request;_logger.LogInformation($"Incoming request: {request.Method}, {request.Path}, [{HeadersToString(request.Headers)}]");await _next.Invoke(context);var response = context.Response;_logger.LogInformation($"Outgoing response: {response.StatusCode}, [{HeadersToString(response.Headers)}]");}private string HeadersToString(IHeaderDictionary headers){var list = new List<string>();foreach(var key in headers.Keys){list.Add($"'{key}':[{string.Join(';', headers[key])}]");}return string.Join(", ", list);}}internal class RequestLoggingOptions{public string[] Exclude = new string[] { };}
}

一对AddRequestLogging()/UseRequestLogging(...)方法使我们能够在微服务中启用查询日志记录。UseRequestLogging方法还可以采用任意数量的异常路径。我们在ShoppingCart和ActivityLogger中使用它来从日志中排除事件轮询,并避免日志溢出。但是,与其他任何常见功能一样,日记功能也是开发人员的专有责任,并且是在特定项目的框架内实现的。

应用测试

我们启动该解决方案,在左侧看到要添加到购物篮中的产品列表,在右侧看到一个空购物篮,并在下面看到操作历史记录,到目前为止也是空的。

在微服务的控制台中,我们看到在启动时,UI已经请求并接收了一些数据。例如,要获取产品列表,发送了一个请求http://localhost:5000/api/products/get,该请求已重定向到http://localhost:5001/get

同样,用户界面从ShoppingCart接收了订单篮的状态。

单击添加按钮时,产品将添加到购物篮中。如果已添加此产品,则数量会增加。

向微服务ShoppingCart发送请求http://localhost:5002/addorder/<productid>

但是由于ShoppingCart不存储产品列表,因此它从ProductCatalog接收有关订购产品的信息。

请注意,在向ProductCatalog发送请求之前,已分配了相关令牌。这使我们能够在失败的情况下跟踪相关查询的链。

操作完成后,ShoppingCart发布一个事件,该事件跟踪并记录ActivityLogger。反过来,UI会定期轮询此微服务,并在操作历史记录面板中显示接收到的数据。当然,历史记录中的条目会出现一些延迟,因为它是一种并行机制,不依赖于添加产品的操作。

结论

Nuget软件包Shed.CoreKit.WebApi使我们能够:

  • 完全专注于开发应用程序的业务逻辑,而无需在微服务交互问题上做出额外的努力;
  • 使用.NET接口描述微服务的结构,并将其用于微服务本身的开发以及用于生成Web客户端(用于微服务的Web客户端是通过在IoC容器中注册接口后通过工厂方法生成的,作为依赖提供);
  • 在IoC容器中将微服务接口注册为依赖项;
  • 无需重新开发UI,就可以将请求从Web UI重定向到微服务。

缺点

通过Web api进行交互的想法非常吸引人,但是存在一些问题:

  • 我们必须注意提供微服务所依赖的微服务配置;
  • 如果我们的微服务超载,我们想启动该微服务的其他几个实例以减少负载。

为了解决这个问题,我们需要另一种架构,即星形架构,即通过公共总线进行交互。

如何通过诸如MQ服务之类的通用总线来组织交互,我们将在下一篇文章中考虑。

.NET Core:通过Web API进行微服务交互相关推荐

  1. ASP.NET Core和Web API:用于管理异常和一致响应的自定义包装器

    目录 介绍 为什么? 怎么做? VMD.RESTApiResponseWrapper Nuget软件包 安装及使用 ASP.NET Core集成 ASP.NET Web API集成 样本响应输出 定义 ...

  2. 别再管你的API叫微服务了

    你有没有听过这句名言:"计算机科学领域只有两个难题,缓存失效和命名"?据说这句话是Phil Karlton在1996年或1997年左右说的.围绕这句格言确实出现了很多带有喜剧色彩的 ...

  3. grpc入门到精通_gRPC[go语言]大师教程,构建现代化Api和微服务

    gRPC [Golang] Master Class Build Modern API & Microservices gRPC[go语言]大师教程,构建现代化Api和微服务 比REST AP ...

  4. C#编写ASP.NET Core的Web API并部署到IIS上的详细教程(API用于准确获取Word/Excel/PPT/PDF的页数)6 -将项目部署到IIS,及常见错误解决方案

    C#编写ASP.NET Core的Web API并部署到IIS上的详细教程(API用于准确获取Word/Excel/PPT/PDF的页数)6 -将项目部署到IIS,及常见错误解决方案 1.前言 2.安 ...

  5. java api gateway_微服务中的 API 网关(API Gateway)

    背景 我们知道在微服务架构风格中,一个大应用被拆分成为了多个小的服务系统提供出来,这些小的系统他们可以自成体系,也就是说这些小系统可以拥有自己的数据库,框架甚至语言等,这些小系统通常以提供 Rest ...

  6. 微服务介绍及Asp.net Core实战项目系列之微服务介绍

    0.目录 整体架构目录:ASP.NET Core分布式项目实战-目录 一.微服务选型 在做微服务架构的技术选型的时候,以"无侵入"和"社区活跃"为主要的考量点, ...

  7. go web框架_golang微服务框架go-micro 入门笔记2.2 micro工具之微应用利器micro web

    micro web micro 功能非常强大,本文将详细阐述micro web 命令行的功能 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go- ...

  8. Kubernetes原生api部署微服务5-监听Pod

    我们使用Client-go中的informer来监听用户微服务与文章微服务的Pod. Informer代替Controller去访问k8s-apiserver,而Controller的所有操作(如:查 ...

  9. 使用angular4和asp.net core 2 web api做个练习项目(二), 这部分都是angular

    上一篇: http://www.cnblogs.com/cgzl/p/7755801.html 完成client.service.ts: import { Injectable } from '@an ...

最新文章

  1. 一年一度程序员“补课”季来袭,618 背后技术大公开!
  2. python从入门到精通视频教程百度云-python从入门到精通视频教程百度云资源
  3. mysql自增mybatis返回主键_Mybatis + mysql 返回自增主键
  4. 端计算(9)-android opencv(1)
  5. 深度协同过滤:用神经网络取代内积建模
  6. Compound Words UVA - 10391(c++用法中substr函数用法+map实现)
  7. TCP/IP协议模型
  8. python pip处理
  9. 好看的css3用户基本信息卡片样式源码
  10. 并发编程(读书笔记)
  11. eclipse-阶段三-Server被关闭后消失,如何打开
  12. 【渝粤教育】国家开放大学2019年春季 1152教育法学 参考试题
  13. Robotium测试报告的生成方法(下)
  14. 解决IE7中移动文件夹无法收藏问题
  15. 2021年BATJ30套大厂Android经典高频面试题,附答案
  16. Excel创建堆积柱形混合折线图
  17. win10环境socks代理实战
  18. iPhone 13发布前迎来坏消息,摩托车会损坏手机相机?
  19. 机器视觉/图像处理可能用到的三方库(各个库的优缺点)
  20. matlab julia分形图,Three.js 朱丽亚集(Julia set)分形图案

热门文章

  1. 数据流中的中位数java_数据流中的中位数
  2. 门面设计模式php,学习php设计模式 php实现门面模式(Facade)
  3. syslog工具_07 Docker 可视化管理和监控工具
  4. java stdout库_Java重写StdOut并将日语写入文件
  5. 高清动漫壁纸,满足动漫视觉控的你
  6. 设计师交流社区,在集设原创作品通过交流发现问题,不断进步!
  7. datetimepicker 默认时间_Django项目中如何使用日期时间选择器DateTimePicker
  8. webstorm代码行数统计_来测试下 2019 你一共写了多少行代码?
  9. C语言和C++中Struct区别
  10. GIS生涯个人总结及个人感悟