The Cybtans Command Line Interface aka Cybtans CLI is a cross-platform Protocol Buffers compiler for creating RESTfull APIs using definitions in the Protobuff Language. The main advantage this tool provides is that it frees the developer from common boilerplate code. This allows the developer to focus on the business logic, resulting in a clean architecture with autogenerated clients components to be used in React ,Angular, Blazor, Xamarin or WPF apps. Thus improving the integration between backend and frontend development teams, due to the client libraries are auto-documented from the protobuf definitions.
On the other hand you can use the cybtans cli to support the development of services with GRPC. Some use cases includes generating protobuff definitions from .NET assemblies along with mapping code to convert between protobuff models and POCO. Also you can autogenerate REST APIs that proxy incoming request to your GRPC services.
In addition the cybtans-cli allows you to create a microservice project structure ready for development. For example run the following command.
> cybtans-cli service -n MyMicroservice -o ./MyMicroservice -sln .\MySolution.sln
This will generate the following projects structure using a convention described below:
- MyMicroservice.Clients: This NET Standard project contains typed REST client using Refit. You can use these clases for consuming your endpoints in integration tests, mvc, blazor or xamaring apps for example. REST client are autogenerated from protbuff services.
- MyMicroservice.Models This NET Standard project contains the services models, aka the data exposed throught the Web API. You can use these models with the .NET client libraries. Model classes are autogenerated from protobuff messages.
- MyMicroservice.Data This project contains your database entities or data layer. You can define Repository interfaces here. You can use the entities defined in this project to autogenerate proto definitions like message and services.
- MyMicroservice.Data.EntityFramework This project contains Entity Framework dependant code like DbContexts, Database Mappings and Repository implementations
- MyMicroservice.RestApi This project contains the Rest API code. You only need to take care of registering the additional dependencies , setup middlewares, etc. Api Controllers are autogenerated from protobuff services.
- MyMicroservice.Services This project is where your business logic goes. Service interfaces are autogenerated. You need to implement the interfaces.
- MyMicroservice.Services.Tests
This project is to create unit or integration tests. If using Integration Tests, you can use the
MyMicroservice.Clients
classes for calling your endpoints using an in-memory test server. - Proto This folder contains definitions in the protobuff language for messages and services. The cli generates models, clients, controllers, services from these proto definitions.
Note that this is a useful command and it is not necessary to use this architectural convention. The cybtans cli tool uses configuration files to override the default conventions so that you can organize your project the way you want and generate the code that best suits your needs or style.
In order to generate REST APIs using Protobuff files, download the cybtans cli for your platform. Extract it and optional add the locating directory to your PATH.
Next create a file named cybtans.json
in your solution. In case you use the cybtans-cli for generating the projects the file is already generated for you. If you want to use .NET 5 just change netcoreapp3.1
for net5.0
{
"Service": "MyMicroservice",
"Steps":[
{
"Type": "messages",
"Output": ".",
"ProtoFile": "./Proto/Data.proto",
"AssemblyFile": "./MyMicroservice.Data/bin/Debug/net5.0/MyMicroservice.Data.dll"
},
{
"Type": "proto",
"Output": ".",
"ProtoFile": "./Proto/MyMicroservice.proto"
}
]
}
This cybtans.json
defines configuration settings like the microservice name and steps, each step represents a code generation process. In the example above there are two steps, one for generating a proto file with messages from a .dll containing the entities, and the second for generating C# code from a proto file that includes the proto generated in the first step.
For example lets define the following entity in the MyMicroservice.Data
project.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Cybtans.Entities;
[Description("Customer Entity")]
[GenerateMessage("CustomerDto")]
public class Customer
{
public Guid Id {get; set;}
[Description("Customer's Name")]
[Required]
public string Name { get; set; }
[Description("Customer's FirstLastName")]
public string FirstLastName { get; set; }
[Description("Customer's SecondLastName")]
[Obsolete]
public string SecondLastName { get; set; }
[Description("Customer's Profile Id, can be null")]
public Guid? CustomerProfileId { get; set; }
public virtual CustomerProfile CustomerProfile { get; set; }
[MessageExcluded]
public virtual ICollection<Order> Orders { get; set; } = new HashSet<Order>();
}
Run the cybtans cli from the solution directory using the folowing command.
> cybtans-cli .
This command generates the Proto/data.proto
file with the following definition.
message CustomerDto {
option description = "Customer Entity";
string name = 1 [required = true, description = "Customer's Name"];
string firstLastName = 2 [description = "Customer's FirstLastName"];
string secondLastName = 3 [description = "Customer's SecondLastName", deprecated = true];
guid customerProfileId = 4 [optional = true, description = "Customer's Profile Id, can be null"];
CustomerProfileDto customerProfile = 5;
guid id = 6;
datetime createDate = 7 [optional = true];
datetime updateDate = 8 [optional = true];
}
In addition you can generate a service definition by using the attribute [GenerateMessage("CustomerDto", Service = ServiceType.Interface)]
. This generates request messages a a service with CRUD operations as shown below.
message GetAllRequest {
string filter = 1 [optional = true];
string sort = 2 [optional = true];
int32 skip = 3 [optional = true];
int32 take = 4 [optional = true];
}
message GetCustomerRequest {
guid id = 1;
}
message UpdateCustomerRequest {
guid id = 1;
CustomerDto value = 2 [(ts).partial = true];
}
message DeleteCustomerRequest{
guid id = 1;
}
message GetAllCustomerResponse {
repeated CustomerDto items = 1;
int64 page = 2;
int64 totalPages = 3;
int64 totalCount = 4;
}
message CreateCustomerRequest {
CustomerDto value = 1 [(ts).partial = true];
}
service CustomerService {
option (prefix) ="api/Customer";
rpc GetAll(GetAllRequest) returns (GetAllCustomerResponse){
option method = "GET";
};
rpc Get(GetCustomerRequest) returns (CustomerDto){
option template = "{id}";
option method = "GET";
};
rpc Create(CreateCustomerRequest) returns (CustomerDto){
option method = "POST";
};
rpc Update(UpdateCustomerRequest) returns (CustomerDto){
option template = "{id}";
option method = "PUT";
};
rpc Delete(DeleteCustomerRequest) returns (void){
option template = "{id}";
option method = "DELETE";
};
}
You can control what properties are included in the protobuff message. Use the [MessageExcluded]
attribute to exclude properties from the message definition. These attributes are located in the Cybtans.Entities.Proto
package. Install this package with
> dotnet add package Cybtans.Entities.Proto --version 1.2.1
Finally in the main proto MyMicroservice.proto
, the one that was specified in the cybtans.json
import the Data.proto
as shown below:
syntax = "proto3";
import "Data.proto";
package MyMicroservice;
// Add addtional services and messages here
.......
.......
All the supported options by the cybtans-cli are located in the cybtans.proto file as a reference. It's not required to include this file if using the cybtans-cli for generating REST APIs. But it's mandatory to include this file when using the GRPC compiler.
As mentioned before you can generate typescript clients for React and Angular as shown below.
{
"Service": "MyMicroservice",
"Steps":[
{
"Type": "messages",
"Output": ".",
"ProtoFile": "./Proto/Data.proto",
"AssemblyFile": "./MyMicroservice.RestApi/bin/Debug/netcoreapp3.1/MyMicroservice.Data.dll"
},
{
"Type": "proto",
"Output": ".",
"ProtoFile": "./Proto/MyMicroservice.proto",
"Gateway": "./App.Gateway/Controllers/MyMicroservice",
"Clients": [
{
"Output": "./react-app/src/services/my-microservice",
"Framework": "react"
},
{
"Output": "./angular-app/src/app/services/my-microservice",
"Framework": "angular"
}
]
}
]
}
You can control how to generate code from the proto files. For example specifiying output directories and namespaces that fits your code organization:
{
"Service": "MyWebAPI",
"Steps": [
{
"Type": "proto",
"ProtoFile": "./Proto/api.proto",
"Models": {
"Output": "./MyWebAPI/Generated/Models",
"Namespace": "Api.Models"
},
"Services": {
"Output": "./MyWebAPI/Generated/Services",
"Namespace": "Api.Services.Definitions"
},
"Controllers": {
"Output": "./MyWebAPI/Generated/Controllers",
"Namespace": "Api.Controllers"
},
"CSharpClients":{
"Output": "../MyWebAPIGateway/Generated/Clients",
"Namespace": "Api.Clients"
},
"GatewayOptions":{
"Output": "../MyWebAPIGateway/Generated/Controllers",
"Namespace": "Api.Controllers"
},
"Clients": [
{
"Output": "../WebApp/src/app/services",
"Framework": "angular"
}
]
}
]
}
You can use the cybtans cli with the GRPC compiler in order to generate messages from .NET assemblies and mapping code. For example add the following settings to the cybtans.json
{
"Steps": [
{
"Type": "messages",
"Output": ".",
"ProtoFile": "./protos/data.proto",
"AssemblyFile": "../MyGrpcService.Data/bin/Debug/net5.0/MyGrpcService.Data.dll",
"Grpc": {
"Enable": true,
"MappingOutput": "../MyGrpcService.Service/Mappings",
"MappingNamespace": "MyGrpcService.Service",
"GrpcNamespace": "MyGrpcService.Service"
}
}
]
}
Using this configuration the cybtans cli generates a protoc (google protobuff compiler) compatible file that looks like this:
syntax = "proto3";
option csharp_namespace = "MyGrpcSerivice.Service";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
message CustomerDto {
option description = "Customer Entity";
string name = 1;
string firstLastName = 2;
string secondLastName = 3;
string customerProfileId = 4;
CustomerProfileDto customerProfile = 5;
string id = 6;
google.protobuf.Timestamp createDate = 7 [optional = true];
google.protobuf.Timestamp updateDate = 8 [optional = true];
}
In addition the cybtans cli allows you to generate REST API Controllers that calls your grpc services. For example define the following cybtans.json
.
{
"Service": "Cybtans.Tests.Grpc",
"Steps": [
{
"Type": "proto",
"ProtoFile": "../Proto/greet.proto",
"Models": {
"Output": "../Cybtans.Tests.Gateway/Generated/Models",
"Namespace": "Cybtans.Tests.Gateway.Models",
"UseCytansSerialization": false
},
"Services": {
"Output": "../Cybtans.Tests.Gateway/Generated/Repository/Definitions",
"Namespace": "Cybtans.Test.Gateway.Repository.Definition",
"Grpc": {
"Output": "../Cybtans.Tests.Gateway/Generated/Repository/Implementation",
"Namespace": "Cybtans.Test.Gateway.Repository.Implementation",
"AutoRegister" : true
}
},
"Controllers": {
"Output": "../Cybtans.Tests.Gateway/Controllers/Grpc"
},
"Clients": [
{
"Output": "../react-app/src/services/grpc",
"Framework": "react"
},
{
"Output": "../angular-app/src/app/services/grpc",
"Framework": "angular"
}
]
}
]
}
Then create a Grpc project with a proto file as shown below.
syntax = "proto3";
option csharp_namespace = "Cybtans.Tests.Grpc";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/empty.proto";
//important to add this file for grpc
import "cybtans.proto";
package greet;
// The greeting service definition.
service Greeter {
option (prefix) = "greeter";
//important add this seeting to generate REST Api Controllers
option (grpc_proxy) = true;
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply){
option (template) = "hello";
option (method) = "GET";
}
}
message HelloRequest {
string name = 1;
}
// The response message containing the greetings.
message HelloReply {
string message = 1;
HellowInfo info = 2;
}
message HellowInfo {
int32 id = 1;
Type type = 3;
InnerA innerA = 4;
enum Type{
A = 0;
B = 1;
}
message InnerA {
InnerB b = 1;
message InnerB {
Type type = 1;
enum Type{
A = 0;
B = 1;
}
}
}
}
Then run the cybtans cli with > cybtans-cli .
and as a result you will find the following files generated for you.
In the API Gateway project remember to register the Grpc clients. For example as shown below:
services.AddGrpcClient<Greeter.GreeterClient>(o =>
{
o.Address = new Uri(Configuration["GreteerServiceUrl"]);
});
Note that there is an autogenerated ProtobuffMppingExtensions.cs
file. This file contains extension methods to convert from POCO objects to Protobuff messages and the other way around.
More examples can be found in the Cybtans SDK Repository. The Cybtans SDK project contains several packages which supports the development with the cybtans cli.
If you like this project you are welcome to give your support at https://paypal.me/anselcastrocabrera.
Powered By Cybtans www.cybtans.com
Copyright (c) 2021 Ansel Castro