silky微服務的應用服務和服務條目

Silky發表於2021-11-16

服務的定義

服務介面是微服務定義服務的基本單位,定義的應用服務介面可以被其他微服務引用,其他微服務通過rpc框架與該微服務進行通訊。

通過ServiceRouteAttribute特性對一個介面進行標識即可成為一個應用服務介面。

例如:

[ServiceRoute]
public interface ITestAppService
{
}

雖然我們通過使用[ServiceRoute]特性可以對任何一個介面標識為一個服務,服務定義的方法會通過應用服務的模板和方法特性的模板生成對應的webapi(該方法沒有服務特性標識為禁用外網)。但是良好的命名規範可以為我們構建服務省去很多不必要的麻煩(通俗的說就是:約定大約配置)。

一般地,我們推薦使用AppService作為定義的服務的字尾。即推薦使用IXxxxAppService作為應用服務介面名稱,預設生成的服務模板為:api/{appservice},使用XxxxAppService作為應用服務實現類的名稱。

路由特性(ServiceRouteAttribute)可以通過template對服務路由模板進行設定。路由模板可以通過{appservice=templateName}設定服務的名稱。

屬性名稱 說明 預設值
template 在對服務介面標識為服務路由時,可以通過[ServiceRoute("{appservice=templateName}")]指定應用服務介面的路由模板。templateName的預設值名稱為對應服務的名稱 api/{appservice}

服務條目

服務條目(ServiceEntry): 服務介面中定義的每一個方法都會生成微服務叢集的一個服務條目。對微服務應用本身而言,服務條目就相當於MVC中的Action,應用服務就相當於Controller

根據服務條目生成WebAPI

應用介面被web主機應用或是閘道器引用後,會根據服務應用介面的路由模板和服務條目方法的Http動詞特性指定的路由資訊或是方法名稱生成相應的webapi,服務條目生成的WebAPI支援restfulAPI風格。

服務條目生成webapi的規則為:

  1. 禁止叢集外部訪問的服務條目([Governance(ProhibitExtranet = true)])不會生成webapi;

  2. 可以通過 [ServiceRoute("{appservice=templateName}")]為應用介面指定統一的路由模板;

  3. 如果服務條目方法沒有被http謂詞特性標識,那麼生成的webapi的http請求動詞會根據服務條目的方法名稱生成,如果沒有匹配到相應的服務條目方法,則會根據服務條目的方法引數;

  4. 服務條目方法可以通過http謂詞特性進行標識,並且http謂詞特性還支援對服務條目指定路由模板,路由模板的指定還支援對路由引數進行約束;


服務條目生成的webAPI = 應用介面條目路由模板 + “方法名稱||Http特性指定的路由特性”

如果不存在Http謂詞特性標識情況下,生成的webapi路由說明(例如,應用介面名稱為:ITestAppService,路由模板未被改寫):

方法名稱 生成的webAPI路徑 Http請求動詞
GetXXX /api/test get
SearchXXX /api/test/search get
CreateXXX /api/test post
UpdateXXX /api/test put
DeleteXXX /api/test delete

存在Http謂詞情況下,生成的webapi的請求動詞會根據服務條目標識的http謂詞特性來決定,開發者還可以通過http謂詞特性為服務條目的的路由進行調整,並且支援路由引數的形式,例如:

方法名稱 生成的webAPI路徑 http謂詞特性 Http請求動詞
GetXXX /api/test/{id} [HttpGet("{id:strig}")] get
DeleteXXX /api/test/name/{name} [HttpDelete("name/{name:strig}")] delete
UpdateXXX /api/test/email [HttpPatch("email")] patch
CreateXXX /api/test/user [HttpPost("user")] post

服務條目的治理特性

開發者可以通過配置檔案對服務條目的治理進行統一配置,除此之外可以通過Governance特性為服務條目方法進行標識,通過其屬性對服務條目進行治理。通過Governance特性對服務條目方法註解後,服務條目的治理屬性將會被該特性重寫。

服務條目治理的屬性請參考服務條目治理屬性配置

快取攔截

在服務應用介面被其他微服務應用引用後,可以通過快取攔截特性(GetCachingIntercept)在rpc通訊過程中,直接從分散式快取中讀取資料,避免通過網路通訊,而從提高系統效能。

更多關於快取攔截請參考快取攔截

服務條目的例子

    [ServiceRoute]
    public interface ITestAppService
    {
        ///  新增介面測試([post]/api/test)
        [GetCachingIntercept("name:{0}")]
        Task<TestOut> Create(TestInput input);

        ///  更新介面測試([put]/api/test)
        Task<string> Update(TestInput input);

        /// 刪除介面測試([delete]/api/test)
        [RemoveCachingIntercept("ITestApplication.Test.Dtos.TestOut", "name:{0}")]
        [Transaction]
        Task<string> Delete([CacheKey(0)] string name);

        /// 查詢介面測試([get]/api/test/search)
        [HttpGet]
        Task<string> Search([FromQuery] TestInput query);

        /// 以表單格式提交資料([post]/api/test/form)
        [HttpPost]
        string Form([FromForm] TestInput query);

        /// 通過name獲取單條資料([get]/api/test/{name:string},以path引數傳參,並約束引數型別為string)
        [HttpGet("{name:string}")]
        [Governance(ShuntStrategy = AddressSelectorMode.HashAlgorithm)]
        [GetCachingIntercept("name:{0}")]
        Task<TestOut> Get([HashKey] [CacheKey(0)] string name);

        /// 通過id獲取單條資料([get]/api/test/{id:long},以path引數傳參,並約束引數型別為long)
        [HttpGet("{id:long}")]
        [Governance(ShuntStrategy = AddressSelectorMode.HashAlgorithm)]
        [GetCachingIntercept("id:{0}")]
        Task<TestOut> GetById([HashKey] [CacheKey(0)] long id);

        ///更新部分資料,使用patch請求 ([patch]/api/test)
        [HttpPatch]
        Task<string> UpdatePart(TestInput input);
    }

服務的實現

一般地,開發者應當將定義服務的介面和服務的實現分開定義在不同的程式集。應用服務介面程式集可以被打包成Nuget包或是以專案的形式被其他微服務應用引用,這樣其他微服務就可以通過rpc代理的方式與該微服務應用進行通訊。更多RPC通訊方面的文件請參考

一個服務介面可以有一個或多個實現類。只有應用介面在當前微服務應用中存在實現類,該微服務應用介面對應的服務條目才會生成相應的服務路由,並將服務路由資訊註冊到服務註冊中心,同時其他微服務應用的例項會訂閱到微服務叢集的路由資訊。

應用介面如果存在多個實現類的情況下,那麼應用介面的實現類,需要通過ServiceKeyAttribute特性進行標識。ServiceKeyAttribute存在兩個引數(屬性)。

屬性名稱 說明 備註
name 服務實現類的名稱 通過webapi請求時,通過請求頭serviceKey進行設定;rpc通訊中,可以通過ICurrentServiceKey的例項調整要請求的應用介面實現。
weight 權重 如果通訊過程中,未指定serviceKey,那麼,會請求權重最高的應用介面的實現類

例項:

/// 應用服務介面(如:可定義在ITestApplication.csproj專案)
[ServiceRoute]
public interface ITestAppService
{
    Task<string> Create(TestInput input);
   // 其他服務條目方法略
}


//------------------------------------//
/// 應用服務例項類1 (如:可定義在TestApplication.csproj專案)
[ServiceKey("v1", 3)]
public class TestAppService : ITestAppService
{
  public Task<string> Create(TestInput input)
  {
      return Task.FromResult("create v1")
  }
  // 其他介面實現方法略
}

//------------------------------------//
/// 應用服務例項類2  (如:可定義在TestApplication.csproj專案)
[ServiceKey("v2", 1)]
public class TestV2AppService : ITestAppService
{
  public Task<string> Create(TestInput input)
  {
      return Task.FromResult("create v2")
  }
   // 其他介面實現方法略
}

生成的swagger文件如下:

在rpc通訊過程中,可以通過IServiceKeyExecutor的例項設定要請求的應用介面的serviceKey

private readonly IServiceKeyExecutor _serviceKeyExecutor;

public TestProxyAppService(ITestAppService testAppService,
    IServiceKeyExecutor serviceKeyExecutor)
{
    _testAppService = testAppService;
    _serviceKeyExecutor = serviceKeyExecutor;
}

public async Task<string> CreateProxy(TestInput testInput)
{
   return await _serviceKeyExecutor.Execute(() => _testAppService.Create(testInput), "v2");
}

開源地址

線上文件

相關文章