Создайте файл .proto общих служб с помощью protobuf-net.Grpc

#protobuf-net #protobuf-net.grpc

Вопрос:

Я пытаюсь создать .proto этой структуры:

— МОДЕЛИ —
базовая модель

 [DataContract]
public abstract class Base
{
   [ProtoMember(1)]
   public string Id { get; set; }

   [ProtoMember(2, DataFormat = DataFormat.WellKnown)]
   public DateTime CreatedDate { get; private set; } = DateTime.UtcNow;

   [ProtoMember(3, DataFormat = DataFormat.WellKnown)]
   public DateTime UpdatedDate { get; set; } = DateTime.UtcNow;
}         
 

Модель задачи

 [ProtoContract]
public class Todo : Base
{
   [ProtoMember(1)]
   public string Title { get; set; }

   [ProtoMember(2)]
   public string Content { get; set; }
 
   [ProtoMember(3)]
   public string Category { get; set; }
}      
 

Плюс эта строка:

 RuntimeTypeModel.Default[typeof(Base)].AddSubType(42, typeof(Todo));
 

— КОНТРАКТЫ —
Базовый контракт

 [ServiceContract]
public interface IBaseService<T>
{
   // CREATE
   [OperationContract]
   Task<RStatus> CreateOneAsync(T request,CallContext context = default);
   
   // FIND
   [OperationContract]
   ValueTask<T> GetById(UniqueIdentification request,CallContext context = default);
}        
 

Контракт на выполнение задач

 [ServiceContract]
public interface ITodoService : IBaseService<Todo>
{
   // FIND        
   [OperationContract]
   ValueTask<Todo> GetOneByQueryAsync(Query query, CallContext context = default);
}          
 

С помощью этого общего подхода я пытаюсь предотвратить повторение кода.

— Startup.cs —

      ...
endpoints.MapGrpcService<TodoService>();
endpoints.MapCodeFirstGrpcReflectionService();
     ...       
 

Итак, когда я запускаю это :

 var schema = generator.GetSchema<ITodoService>();
 

Я получаю этот вывод в файле .proto:

 syntax = "proto3";
package Nnet.Contracts;
import "google/protobuf/timestamp.proto";

message Base {
   string Id = 1;
   .google.protobuf.Timestamp CreatedDate = 2;
   .google.protobuf.Timestamp UpdatedDate = 3;
   oneof subtype {
     Todo Todo = 42;
   }
}
message IEnumerable_Todo {
   repeated Base items = 1;
}
message Query {
   string Filter = 1;
}
message Todo {
   string Title = 1;
   string Content = 2;
   string Category = 3;
}
service TodoService {
   rpc GetOneByQuery (Query) returns (Base);
}
    
 

В разделе файла .proto service Todoservice мне не хватает двух других функций из базового контракта. Кроме того, тип возвращаемой функции rpc GetOneByQuery (Query) returns (Base); неверен, это должно быть Todo.

Есть какие-нибудь предложения?

Ответ №1:

Кроме того, возвращаемый тип функции rpc GetOneByQuery (Запрос) возвращает (базовый); неверен, это должно быть Todo.

Нет, это правильно; сам по себе protobuf не имеет понятия наследования — protobuf-net должен вставить его, что он и делает, используя инкапсуляцию, следовательно Base , с a oneof subtype , у которого есть a Todo . В вашем случае мы ожидаем, что переданное всегда будет фактически разрешаться как a Todo , но язык .proto schema не имеет синтаксиса для выражения этого. Абсолютное лучшее, что мы могли бы здесь сделать, — это включить дополнительный комментарий в сгенерированное высказывание .proto // return type will always be a Todo или подобное.

Мне не хватает двух других функций в Базовом контракте

наследование услуг и универсальных услуг в настоящее время не поддерживаются; опять же, эти понятия, не имеющие соответствующих метафор .Proto или gRPC в целом, и protobuf-нетто будет нужно придумывать что-то подходящее; я не — на сегодняшний день — сел и подумал через любую такую схему или нет-последствия. По сути, проблема здесь заключается в том, что договор на обслуживание и имя используются для построения пути/URL-адреса; когда говорим о единой контрактной службы и метод, это нормально — но когда речь идет о наследовании и обобщения, это становится намного сложнее, чтобы однозначно определить какие услуги ты говоришь, и как это должно карте между маршрута и реализацию (и, действительно,.синтаксис прото). Я полностью открыт для размышлений здесь — на сегодняшний день это просто не было обязательным требованием для критического пути.

Ответ №2:

Что ж, на данный момент все мои сервисы работают на C#, но было бы здорово поддерживать эту схему наследования интерфейса в будущем, если нам придется делиться файлами .proto с другими приложениями, кроме c#. Я не уверен, можно ли вставить код, который может помочь вам поддерживать эту функцию… Итак, это код:
Я пропускаю модели
— КОНТРАКТЫ —

 //Base Contract
public interface IBaseService<T>
{
    Task<RStatus> CreateOne(T request);

    ValueTask<T> GetById(UniqueIdentification request);
}

//Product Contract
public interface IProductService<T> : IBaseService<T>
{
    Task<T> GetByBrandName(string request);

    ValueTask<T> GetByName(string request);
}

    //Audio Contract
public interface IAudioService<T>
{
    Task<T> GetBySoundQuality(int request);
}


//Headphones Contract
public interface IHeadphonesService : IProductService<Headphones>, IAudioService<Headphones>
{
    Task<Headphones> GetByBluetoothOption(bool request);
}             
 

— ПРОГРАММА.CS —

 static void Main(string[] args)
{
    foreach (var type in TypesToGenerateForType(typeof(IHeadphonesService)))
    {
        Console.WriteLine($"Type: {type} n");
    }
}

public static IEnumerable<Type> TypesToGenerateForType(Type type)
{
    foreach (var interfaceType in type.FindInterfaces((ignored, data) => true, null))
    {
        foreach (var dm in interfaceType.GetMethods())
        {
            Console.WriteLine($"Method Name: {dm}");
        }
        yield return interfaceType;
    }
    foreach (var tm in type.GetMethods())
    {
        Console.WriteLine($"Method Name: {tm}");
    }
    yield return type;
}      
    
 

Вывод:

 Method Name: System.Threading.Tasks.Task`1[TestIntInh.Shared.Models.Headphones] GetByBrandName(System.String)
Method Name: System.Threading.Tasks.ValueTask`1[TestIntInh.Shared.Models.Headphones] GetByName(System.String)
Type: TestIntInh.Shared.Contracts.IProductService`1[TestIntInh.Shared.Models.Headphones 

Method Name: System.Threading.Tasks.Task`1[TestIntInh.Shared.Models.RStatus] CreateOne(TestIntInh.Shared.Models.Headphones)
Method Name: System.Threading.Tasks.ValueTask`1[TestIntInh.Shared.Models.Headphones] GetById(TestIntInh.Shared.Models.UniqueIdentification)
Type: TestIntInh.Shared.Contracts.IBaseService`1[TestIntInh.Shared.Models.Headphones] 

Method Name: System.Threading.Tasks.Task`1[TestIntInh.Shared.Models.Headphones] GetBySoundQuality(Int32)
Type: TestIntInh.Shared.Contracts.IAudioService`1[TestIntInh.Shared.Models.Headphones] 

Method Name: System.Threading.Tasks.Task`1[TestIntInh.Shared.Models.Headphones] GetByBluetoothOption(Boolean)
Type: TestIntInh.Shared.Contracts.IHeadphonesService        
    
 

Я думаю, что с помощью этого вы можете создать соответствующий файл .proto. Это FindInterfaces дает вам почти все, что вам может понадобиться.