Создайте файл .proto общих служб с помощью 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;
}         

Модель Todo

      [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);
}        

Контракт Todo

      [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.

Какие-либо предложения?

2 ответа

Также тип возвращаемого значения функции rpc GetOneByQuery (Query) возвращает (Base); неправильно, это должно быть Todo.

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

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

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

Что ж, на данный момент все мои службы находятся на 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);
}             

- PROGRAM.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. В дает вам почти все, что вам может понадобиться.

Другие вопросы по тегам