精选文章 .NET Core:通过Web API进行微服务交互

.NET Core:通过Web API进行微服务交互

作者:寒冰屋 时间: 2020-08-05 08:45:55
寒冰屋 2020-08-05 08:45:55

目录

介绍

命名协议

MicroCommerce应用程序结构

MicroCommerce应用开发

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

2. ProductCatalog项目

3. ShoppingCart项目

4. ActivityLogger项目

5. WebUI项目,用户界面

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

应用测试

结论

缺点


介绍

几乎所有在.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:通过Web API进行微服务交互1

创建一个空的.NET Core解决方案。将WebUI项目添加为空的ASP.NET Core WebApplication。接下来,添加ProductCatalogShoppingCartActivityLog微服务项目,以及空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 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,并且不能来自其他。这同样适用于HttpGetHttpPutHttpPostHttpPatchHttpDeletFromBody属性,如果使用的话。

使用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 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 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 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软件包安装到项目中,并添加到InterfacesMiddleware 项目的链接。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 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 dependency
            services.AddTransient();
            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 endpoint
            app.UseWebApiEndpoint();
        }
    }
}

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

http://localhost: 5001/get
http://localhost: 5001/get/

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

如果我们通过以下方式连接端点:app.UseWebApiEndpoint(“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软件包以及指向InterfacesMiddleware项目的链接添加到项目中。

创建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 _orders = new List();
        private static List _events = new List();
        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 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();
            services.AddTransient();
            // register a dependency binded to the endpoint
            services.AddWebApiEndpoints
            (new WebApiEndpoint(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();
        }
    }
}

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

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

4. ActivityLogger项目

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

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

创建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 _log = new List();

        public ActivityLoggerImpl(IShoppingCart shoppingCart)
        {
            _shoppingCart = shoppingCart;
        }

        public IEnumerable 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();
            services.AddTransient();
            services.AddWebApiEndpoints(new WebApiEndpoint
                     (new System.Uri("http://localhost:5002")));
            // register the scheduler
            services.AddHostedService();
            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();
        }
    }
}

5. WebUI项目,用户界面

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

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

实际上,这是一个普通的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 returned
                if(string.IsNullOrEmpty(context.Request.Path.Value.Trim('/')))
                {
                    context.Request.Path = "/index.html";
                }

                await next();
            });
            app.UseStaticFiles();
            // add redirects to microservices
            app.UseWebApiRedirect("api/products", new WebApiEndpoint
                                 (new System.Uri("http://localhost:5001")));
            app.UseWebApiRedirect("api/orders", new WebApiEndpoint
                                 (new System.Uri("http://localhost:5002")));
            app.UseWebApiRedirect("api/logs", new WebApiEndpoint
                                 (new System.Uri("http://localhost:5003")));
        }
    }
}

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

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

  


    
    
    "
    
    
    
    


    

MicroCommerce

All products

ADD

Shopping cart

DELETE

Operations history

 

//
// 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();
        }

        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();
            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方法还可以采用任意数量的异常路径。我们在ShoppingCartActivityLogger中使用它来从日志中排除事件轮询,并避免日志溢出。但是,与其他任何常见功能一样,日记功能也是开发人员的专有责任,并且是在特定项目的框架内实现的。

应用测试

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

.NET Core:通过Web API进行微服务交互2

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

.NET Core:通过Web API进行微服务交互3

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

.NET Core:通过Web API进行微服务交互4

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

.NET Core:通过Web API进行微服务交互5

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

.NET Core:通过Web API进行微服务交互6

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

.NET Core:通过Web API进行微服务交互7

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

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

结论

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

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

缺点

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

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

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

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

勿删,copyright占位
分享文章到微博
分享文章到朋友圈

上一篇:kubespray部署k8s version 1.0

下一篇:centos6下安装kong

您可能感兴趣

  • 当遇到跨域开发时, 我们如何处理好前后端配置和请求库封装(koa/axios版)

    我们知道很多大型项目都或多或少的采用跨域的模式开发, 以达到服务和资源的解耦和高效利用. 在大前端盛行的今天更为如此, 前端工程师可以通过nodejs或者Nginx轻松搭建起web服务器.这个时候我们只需要请求后端服务器的接口即可实现系统的业务功能开发.这个过程中会涉及到web页面向API服务器的跨域访问(由于受到浏览器的同源策略,但是业界已有很多解决方案,接下来会介绍).通过这种开发模式使...

  • 浅谈微服务体系中的分层设计和领域划分

    引言 看标题感觉这个东西很理论,比起“高并发、多线程”、“分布式CAP、一致性、Paxos”、“高可用SLA”等具体的干货技术点,软件体系知识显得很“湿”,似乎人人都有自己的认识,但又很少有人能说完整,有一点可以确定的是,如果你未来需要独立设计一个复杂的系统中台,并使之未来能快速应对各种需求变化的话,科学合理的领域划分和边界界定需要我们“处女座级”的坚持下去,这对防止人力失控、减少项目烂尾很...

  • Docker 一篇文章带你理解Dockerfile

    关于Dockerfile 在Docker中创建镜像最常用的方式,就是使用Dockerfile。Dockerfile是一个Docker镜像的描述文件,我们可以理解成火箭发射的A、B、C、D…的步骤。Dockerfile其内部包含了一条条的指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。 一个Dockerfile的示例如下所示: #基于centos镜像 FROM cen...

  • GitHub 上值得收藏的 100 个精选前端项目!

    点击上方“逆锋起笔”,公众号回复 PDF 领取大佬们推荐的学习资料 来 源:https://www.jianshu.com/p/72ca8192f7b8 引言 整理与收集的一些比较优秀github项目,方便自己阅读,顺便分享出来,大家一起学习,作者简书上文章会持续更新,版权归原作者所有。 最新更新 codepen 一个在线编辑前端项目的网站,其中有一些前端大神的作品,也有很多令人惊艳的前端效...

  • 面试问题之——给你图片的url,你能知道它所占的字节空间吗?

    从一个需求说起 这标题起得有点标题党,实际情况是我最近在做一个公司内部工具时,遇到了这么一个需求,给定一个静态资源站点上某张图片的url(比如https://a.xxxcdn.com/demo.jpg),如何获取其存储大小并计算出加载该资源的平均网速呢?注意,是存储大小,而不是图片的宽高尺寸大小。接下来就是我在实现这个需求中总结的一些方式。 一、通过Ajax请求获取 这种方式涉及到XMLHt...

  • 建议收藏!2020 年必备的几个 DevOps 工具

    来自:SegmentFault ,作者:徐九 链接:https://segmentfault.com/a/1190000022908614 提到 DevOps 这个词,我相信很多人一定不会陌生。作为一个热门的概念,DevOps近 年来频频出现在各大技术社区和媒体的文章中。到了 2020 年,DevOps 的革命也终于成为了一个主流,DevOps 相关工具的受欢迎程度也在激增。根据 Googl...

  • 深度学习弱爆了!人工智能的路走错了!道翰天琼认知智能机器人大脑平台为您揭秘-1。

    人工智能和认知神经科学都在尝试打开 “智能”的黑箱,两者应相互对话、相互帮助,才能共同快速发展。一方面,脑科学能帮助人工智能专家构思出更好的网络结构、更好的算法,从而推动人工智能的发展;另一方面,我们也经常发现,AI专家发明出的人工智能算法,经常和生物体处理信息的方式极为类似。 那么,人工智能发展到最后真的会变得和生物大脑完全一样吗?不一定,因为两者服务于不同的目的。人工智能要实现的是具有专...

  • 我为什么放弃移动开发?

    点击“开发者技术前线”,选择“星标????” 在看|星标|留言, 真爱 本文由原作者发表在 medium.com,经原作者授权由 InfoQ 中文站翻译并分享。 在这篇文章中,我想要分享我关于 Android SDK 和 Flutter 的糟糕体验。我提到的某些要点也适用于 iOS SDK。我已经在几年前放弃了移动开发的工作,希望后来许多事情已经在朝好的方向改进。但在当时,我发现移动生态系统...

华为云40多款云服务产品0元试用活动

免费套餐,马上领取!
CSDN

CSDN

中国开发者社区CSDN (Chinese Software Developer Network) 创立于1999年,致力为中国开发者提供知识传播、在线学习、职业发展等全生命周期服务。