Manual wheel building -- RPC framework dotnetcore RPC based on. NetCore

Keywords: github Dubbo Attribute

preface

Since there are not so many restrictions between internal services, the most simple and clear way is the most appropriate way. I prefer the way of using Dubbo. I separate the interface layer as the contract of the service. The server provides the service with this set of contract, and the client uses this set of contract to call the service, the same way as using the local method. There are few relatively perfect RPC frameworks like Dubbo on. Net platform. GRPC is indeed a very excellent RPC framework, which can boast of language calls. However, from a personal point of view, it's still difficult to write proto files every time. Nowadays, with the trend of service splitting and microservice architecture prevailing, a simple and practical RPC framework can indeed improve many development efficiency.

brief introduction

As the. Net Core matures and becomes stable, it provides a convenient way for me to achieve this goal. So I used my spare time to write a set of Asp.Net Core's RPC framework is to achieve a small goal of its own. Roughly, the Server side depends on Asp.Net Core, it is more convenient to intercept and process requests in the way of middleware. The Client side can be any host program that can host the. Net Core. The communication mode is HTTP protocol, using HttpClientFactory. As for why HttpClientFactory is used, because HttpClientFactory can realize service discovery more easily, and can integrate Polly very well, which is very convenient for implementation, timeout retry, fuse degradation, which provides a lot of convenience in the development process. Because of my limited ability, based on these convenience, standing on the shoulders of giants, I simply implemented an RPC framework, and the project is hosted on GitHub https://github.com/softlgl/DotNetCoreRpc If you are interested, please refer to it.

development environment

  • Visual Studio 2019
  • .Net Standard 2.1
  • Asp.Net Core 3.1.x

How to use

Open Visual Studio and create a new RPC contract interface layer. My name here is IRpcService. Then create a new Client layer (which can be any host program that can host. Net Core) called ClientDemo, and then create a Server layer (which must be Asp.Net Core project) is called WebDemo, which is attached at the end of the article This article Demo connection The project structure is as follows:

Client configuration

Client side introduction DotNetCoreRpc.Client Package and introduce a custom contract interface layer

<PackageReference Include="DotNetCoreRpc.Client" Version="1.0.2" />

Then you can code happily, roughly as follows

class Program
{
    static void Main(string[] args)
    {
        IServiceCollection services = new ServiceCollection();
        //*Register DotNetCoreRpcClient core services
        services.AddDotNetCoreRpcClient()
        //*The communication is based on HTTP. The internal HttpClientFactory can be registered by itself
        .AddHttpClient("WebDemo", client => { client.BaseAddress = new Uri("http://localhost:13285/"); });

        IServiceProvider serviceProvider = services.BuildServiceProvider();
        //*Get RpcClient to use this class to create specific service proxy objects
        RpcClient rpcClient = serviceProvider.GetRequiredService<RpcClient>();

        //IPersonService is the service package interface I introduced. It needs to provide the ServiceName, which is the name of AddHttpClient
        IPersonService personService = rpcClient.CreateClient<IPersonService>("WebDemo");

        PersonDto personDto = new PersonDto
        {
            Id = 1,
            Name = "yi Between reading",
            Address = "China",
            BirthDay = new DateTime(2000,12,12),
            IsMarried = true,
            Tel = 88888888888
        };

        bool addFlag = personService.Add(personDto);
        Console.WriteLine($"Add results=[{addFlag}]");

        var person = personService.Get(personDto.Id);
        Console.WriteLine($"obtain person result=[{person.ToJson()}]");

        var persons = personService.GetAll();
        Console.WriteLine($"obtain persons result=[{persons.ToList().ToJson()}]");

        personService.Delete(person.Id);
        Console.WriteLine($"Delete complete");

        Console.ReadLine();
    }
}

So far, the Client-side code has been written

Server side configuration

Client side introduction DotNetCoreRpc.Client Package and introduce a custom contract interface layer

<PackageReference Include="DotNetCoreRpc.Server" Version="1.0.2" />

Then write a contract interface implementation class, such as my name is PersonService

//Implement contract interface IPersonService
public class PersonService:IPersonService
{
    private readonly ConcurrentDictionary<int, PersonDto> persons = new ConcurrentDictionary<int, PersonDto>();
    public bool Add(PersonDto person)
    {
        return persons.TryAdd(person.Id, person);
    }

    public void Delete(int id)
    {
        persons.Remove(id,out PersonDto person);
    }

    //Custom Filter
    [CacheFilter(CacheTime = 500)]
    public PersonDto Get(int id)
    {
        return persons.GetValueOrDefault(id);
    }

    //Custom Filter
    [CacheFilter(CacheTime = 300)]
    public IEnumerable<PersonDto> GetAll()
    {
        foreach (var item in persons)
        {
            yield return item.Value;
        }
    }
}

As can be seen from the above code, I have customized the Filter. The Filter here is not Asp.Net The Filter defined by the core framework, but the Filter defined by the DotNetCoreRpc framework, can be customized as follows

//*To inherit from the abstract class RpcFilterAttribute
public class CacheFilterAttribute: RpcFilterAttribute
{
    public int CacheTime { get; set; }

    //*Support attribute injection, which can be public or private
    //*From services here is not Asp.Net  Under core namespace, but from DotNetCoreRpc.Core Namespace
    [FromServices]
    private RedisConfigOptions RedisConfig { get; set; }

    [FromServices]
    public ILogger<CacheFilterAttribute> Logger { get; set; }

    public override async Task InvokeAsync(RpcContext context, RpcRequestDelegate next)
    {
        Logger.LogInformation($"CacheFilterAttribute Begin,CacheTime=[{CacheTime}],Class=[{context.TargetType.FullName}],Method=[{context.Method.Name}],Params=[{JsonConvert.SerializeObject(context.Parameters)}]");
        await next(context);
        Logger.LogInformation($"CacheFilterAttribute End,ReturnValue=[{JsonConvert.SerializeObject(context.ReturnValue)}]");
    }
}

The above code basically completes the operation of the server business code. Next, let's see how to Asp.Net DotNetCoreRpc is configured in the core. Open Startup, and configure the following code

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IPersonService, PersonService>()
        .AddSingleton(new RedisConfigOptions { Address = "127.0.0.1:6379", Db = 10 })
        //*Register DotNetCoreRpcServer
        .AddDotNetCoreRpcServer(options => {
            //*Ensure that the added contract service interface has been registered in the DI container in advance

            //Add contract interface
            //options.AddService<IPersonService>();

            //Or add the contract interface name ending with xxx
            //options.AddService("*Service");

            //Or add contract interface with specific name xxx
            //options.AddService("IPersonService");

            //Or scan the contract interface under the specific namespace
            options.AddNameSpace("IRpcService");

            //Global filters can be added in the same way as CacheFilterAttribute
            options.AddFilter<LoggerFilterAttribute>();
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        //This pile can not be + 1
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        //Add DotNetCoreRpc Middleware
        app.UseDotNetCoreRpc();

        //This pile can not be + 2
        app.UseRouting();

        //This pile can not be + 3
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapGet("/", async context =>
            {
                await context.Response.WriteAsync("Server Start!");
            });
        });
    }
}

The above is the simple use and configuration of the Server. Does it feel very Easy. Attach operational Demo address The specific code can be found in Demo

summary

It is my recent wish to implement a set of RPC framework by myself. Now it can be said that it has been implemented. Although it doesn't look so tall, it still conforms to the idea of RPC as a whole. Mainly want to own the practice of the field, by the way, also hope to provide you with some simple ideas. It's not that I must be right, I may be wrong to say a lot, but what I say is my own experience and thinking, which may bring you a second, a half second of thinking, or even if you think that what I say is reasonable, which can trigger your inner feelings, that's what I do. Finally, welcome to comment area or GitHub of the project Give critical guidance.

Posted by CanMan2004 on Sat, 13 Jun 2020 04:10:01 -0700