gRPC-автогенерація Front-end-у

Привіт, мене звати Ярослав. Я працюю розробником у компанії Evrius. У цій статті розглянемо автогенерацію клієнт-серверної взаємодії на основі добре відомого прикладу, що зацікавить веброзробників.

Маленький ліричний відступ

Це вже п’ята моя стаття на DOU, і, звісно, кожну статтю після публікації я надсилав подивитися друзям і колегам, щоб отримати зворотний зв’язок.

Здебільшого статті друзям подобалися, але й частку критики вдалося здобути: так дізнався, що статті «сухі». І справді, статті схожі на мій код (так само мало коментарів) або на інструкцію, як доїхати від Києва до Львова й назад на велосипеді (знаю лише одного велотуриста, що так може).

Ця стаття теж буде інструкцією, та цього разу писатиму більше пояснень і думок.

Ще одна відмінність від уже написаних статей у тому, що раніше я розглядав завдання, які вже розв’язав, тому процес написання складався з підготовки прикладів коду й подальшого написання статті на основі вже готових прикладів. А в цій публікації мені ще самому треба буде розібратися з grpc-web і зробити інструкцію, яку зможу в майбутньому використовувати.

Чому автогенерація важлива

Автогенерація коду — це перекладання однотипної роботи на комп’ютер (як і має бути) або спосіб уникнення помилок-одруків під час копіювання коду (навіть досвідчені спеціалісти помиляються).

Процес розробки й однотипна робота

Коли тільки починаєш працювати на новій роботі, та ще й з новими технологіями, то є азарт, усе цікаво, працювати приємно й комфортно.

Звикаєш до інструментів: IDE, тестів, CI/CD, каркасу з командами для генерації шаблонів DB-міграцій, CRUD-генераторів і ApiDoc-генераторів.

Звикаєш до того, що процес розробки налаштовано і можна зосередитися на логіці. Звісно, є винятки. Для мене такими були валідації, які робив на стороні сервера, а потім копіював на клієнт, і API методи з моделями, які спершу робив на сервері й знову копіював на клієнт у браузері.

Так, процес розробки, як смуги на зебрі: білі — то цікава розробка нової логіки, а темні — однотипне копіювання вже наявної логічної структури й адаптування під нові критерії (зазвичай таке з радістю роблять початківці).

Правильні метафори

Щоб зрозуміло пояснити технологію, треба вибрати всім добре знайомий приклад.

Сайт з новинами LamerNews використовується як приклад для пояснення Redis-у.

У книжках про архітектуру я часто зустрічав приклади облікових систем. У цій статті для прикладу я виберу форум.

gRPC для міжсервісної взаємодії

gRPC — високопродуктивний каркас для взаємодії між сервісами, що дає можливість згенерувати код клієнта й сервера на основі файлів з розширенням .proto.

Код клієнта й сервера можна згенерувати для різних мов програмування. Proto-файли містять у собі опис повідомлень, що відправляються та отримуються у форматі protobuf. Якщо ви чуєте про protobuf уперше, то вважайте, що це альтернатива JSON-у.

Розгляньмо на основі прикладу про форум, який вигляд матиме proto-файл, де будуть методи для створення нової теми та редагування вже наявної:

syntax = "proto3";

service Forum {
    rpc CreateTopic (CreateTopicRequest) returns (UpdateTopicResponse);
    rpc UpdateTopic (UpdateTopicRequest) returns (UpdateTopicResponse);
}

message UpdateTopicResponse {
    bool success = 1;
    string error_message = 2;
}

message CreateTopicRequest {
    string title = 1;
    string description = 2;
}

message UpdateTopicRequest {
    uint32 code = 1;
    string title = 2;
    string description = 3;
}

Ідентифікатори-назви сервісу та повідомлень можуть бути довільними, індекси мають бути унікальними й використовуватися для серіалізації, і цей приклад може мати також інакший вигляд, застосовуючи одну структуру для створення та оновлення:

syntax = "proto3";

service ForumService {
    rpc TopicCreate (UpdateTopicRequest) returns (UpdateTopicResponse);
    rpc TopicUpdate (UpdateTopicRequest) returns (UpdateTopicResponse);
}

message UpdateTopicResponse {
    bool success = 1;
    string error_message = 2;
}

message UpdateTopicRequest {
    uint32 code = 1;
    string title = 2;
    string description = 3;
}

Або навіть так, виділивши дані в окреме повідомлення і використовуючи як спільний код:

syntax = "proto3";

service ForumService {
    rpc TopicCreate (CreateTopicRequest) returns (UpdateTopicResponse);
    rpc TopicUpdate (UpdateTopicRequest) returns (UpdateTopicResponse);
}

message UpdateTopicResponse {
    bool success = 1;
    string error_message = 2;
}

message Topic {
    string title = 1;
    string description = 2;
}

message CreateTopicRequest {
    Topic data = 1;
}

message UpdateTopicRequest {
    uint32 code = 1;
    Topic data = 2;
}

Я вибрав би перший або другий варіант, коли повідомлення мають мало полів, і третій — коли повідомлення вже має (чи ми сподіваємося, що воно матиме) багато полів.

На основі proto-файлу можна згенерувати повноцінний клієнт під багато платформ; це може бути мікросервіс, мобільний застосунок або ж клієнт у браузері.

Ця стаття саме про клієнт, що застосовуватиметься в браузері.

Як це працює

Ми написали proto-файл forum.proto, на основі якого, за допомогою інструменту protoc, згенерували моделі й інтерфейс; інтерфейс реалізували на сервері, і тепер сервер готовий до використання.

Далі ми копіюємо цей proto-файл forum.proto в репозиторій з Front-end-ом; на його основі за допомогою інструменту protoc і плагіна до нього protoc-gen-grpc-web генеруємо моделі та клієнт, готові до використання.

Коли нам треба буде додати нові поля в уже наявні повідомлення чи додати нові rpc-методи, ми оновимо proto-файл, згенеруємо на сервері код, реалізуємо нову логіку, так само скопіюємо proto-файл forum.proto в репозиторій з Front-end-ом і згенеруємо клієнт.

Таким чином Front-end-розробник матиме готовий до використання gRPC-клієнт, а подивитися proto-файл буде простіше, ніж API документацію.

При описі я зробив спрощення, коли писав про один proto-файл. Зазвичай це тека, де є service.proto, у якому підключаються файли з повідомленнями.

Завдання і технологічний стек

У цій статті я реалізую прототип форуму DOU, заради цікавості додам нових фіч; сервер буде на Go, зберігатиму в MongoDB, запускатиму через docker-compose, а на клієнті буде Vue.js і Webpack.

Якщо ви хочете самі розібратися з gRPC Web, то можете клонувати репозиторій github.com/grpc/grpc-web з простою інструкцією, як запустити:

docker-compose pull
docker-compose up
browse http://localhost:8081/echotest.html

AJAX-лічильник, від простого до складного

Перед тим, як розглядати приклад з gRPC, розгляньмо простіший.

Візьмемо для прикладу вебсторінку, на якій показуємо число запитів; число запитів отримуватимемо через AJAX, а в наступному прикладі замінимо AJAX на gRPC.

Маємо три файли: index.html для зображення вмісту, counter.js, що робить AJAX-запит, та main.go сервер на Go:

 
├── main.go
└── public
    ├── index.html
    └── js
        └── counter.js
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>AJAX counter example</title>
</head>
<body>
    <p>
        The page was viewed <span id="js-counter">0</span> times
    </p>
    <script src="/js/counter.js"></script>
</body>
</html>
{
    fetch("/api/counter.json")
        .then(function (response) {
            return response.json();
        })
        .then(function (json) {
            document.getElementById("js-counter").innerHTML = json.count;
        })
        .catch(console.error);
}
package main

import (
    "encoding/json"
    "log"
    "net/http"
    "sync/atomic"
)

type CounterResponse struct {
    Count uint32 `json:"count"`
}

func main() {
    var counter = uint32(0)

    http.Handle("/", http.FileServer(http.Dir("./public")))
    http.HandleFunc("/api/counter.json", func(w http.ResponseWriter, _ *http.Request) {
        var newCounter = atomic.AddUint32(&counter, 1)

        json.NewEncoder(w).Encode(&CounterResponse{
            Count: newCounter,
        })
    })

    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal(err)
    }
}

Якщо хочете перевірити приклад, треба мати вже встановлений Golang.

go run main

browse http://localhost:8080/

Код AJAX-прикладу доступний у репозиторії.

gRPC-лічильник

У цьому прикладі ми:

  1. Налаштуємо кодогенерацію серверної та клієнтської частини.
  2. Опишемо файл counter.proto, на основі якого згенеруємо код для клієнт-серверної взаємодії.
  3. На стороні сервера реалізуємо інтерфейс лічильника (інтерфейс згенерований через кодогенерацію).
  4. На стороні клієнта під’єднаємо згенерований клієнт і зберемо проєкт через Webpack.

Встановити protoc можна з офіційною інструкцією для Ubuntu (на момент написання статті найсвіжіша версія protoc 3.11.4):

PROTOC_ZIP=protoc-3.11.4-linux-x86_64.zip
curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.11.4/$PROTOC_ZIP
sudo unzip -o $PROTOC_ZIP -d /usr/local bin/protoc
sudo unzip -o $PROTOC_ZIP -d /usr/local 'include/*'
rm -f $PROTOC_ZIP

Інструмента protoc досить для кодогенерації серверної частини на Go.

А ось для кодогенерації клієнтської частини на JavaScript потрібен плагін protoc-gen-grpc-web, що можна встановити на Ubuntu так:

curl -sSL https://github.com/grpc/grpc-web/releases/download/1.0.7/protoc-gen-grpc-web-1.0.7-linux-x86_64 -o /usr/local/bin/protoc-gen-grpc-web chmod +x /usr/local/bin/protoc-gen-grpc-web

Опишемо counter.proto:

syntax = "proto3";

package counter;

message Empty {

}

message Response {
  uint32 count = 1;
}

service CounterSomeServiceName {
  rpc CountSomeMethodName(Empty) returns (Response);
}

Файл counter.proto я розмістив у теці з довільною назвою protos:

 
~/go/src/gitlab.com/go-yp/grpc-counter
└── protos
    └── services
        └── counter
            └── counter.proto

Назвав CounterSomeServiceName і CountSomeMethodName, щоб було простіше побачити, які суфікси та префікси додаються після кодогенерації.

Згенеруємо код для серверної частини:

mkdir -p ./models
protoc -I . protos/services/counter/*.proto --go_out=plugins=grpc:models

Оскільки кодогенерацію ви будете запускати після оновлення proto-файлів, рекомендую зберігати в Makefile:

proto-server:
    mkdir -p ./models
    protoc -I . protos/services/counter/*.proto --go_out=plugins=grpc:models

make proto-server

Після кодогенерації proto-server отримаємо файл counter.pb.go:

~/go/src/gitlab.com/go-yp/grpc-counter
├── Makefile
├── protos
│   └── services
│       └── counter
│           └── counter.proto
└── models
    └── protos
        └── services
            └── counter
                └── counter.pb.go [+]

У файлі counter.pb.go буде згенерований код моделей Response і Empty та методи цих моделей:

// Code generated by protoc-gen-go. DO NOT EDIT.
// source: protos/services/counter/counter.proto

package counter

// ...

type Response struct {
    Count                uint32   `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

// ...

type Empty struct {
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

// ...

Методи моделей Response і Empty — звичайні обгортки для реалізації службових інтерфейсів серіалізації protobuf-у через рефлексію, тому я їх прибрав з прикладу.

Також у counter.pb.go нам буде цікавий інтерфейс сервісу та реєстрації:

package counter

// ...

type CounterSomeServiceNameServer interface {
    CountSomeMethodName(context.Context, *Empty) (*Response, error)
}

func RegisterCounterSomeServiceNameServer(s *grpc.Server, srv CounterSomeServiceNameServer) {
    // ...
}

Тепер реалізуємо інтерфейс CounterSomeServiceNameServer:

package main

import (
    "context"
    "gitlab.com/go-yp/grpc-counter/models/protos/services/counter"
    "sync/atomic"
)

type counterServer struct {
    count uint32
}

func (s *counterServer) CountSomeMethodName(context.Context, *counter.Empty) (*counter.Response, error) {
    var newCount = atomic.AddUint32(&s.count, 1)

    return &counter.Response{
        Count: newCount,
    }, nil
}

var _ counter.CounterSomeServiceNameServer = new(counterServer)

Реалізуємо й запустимо на 50551-порті gRPC-сервер (також залишимо static-server з прикладу про AJAX-лічильник):

package main

import (
    "context"
    "gitlab.com/go-yp/grpc-counter/models/protos/services/counter"
    "log"
    "net"
    "net/http"
    "sync/atomic"

    "google.golang.org/grpc"
)

type counterServer struct {
    count uint32
}

func (s *counterServer) CountSomeMethodName(context.Context, *counter.Empty) (*counter.Response, error) {
    var newCount = atomic.AddUint32(&s.count, 1)

    return &counter.Response{
        Count: newCount,
    }, nil
}

var (
    mainServer counter.CounterSomeServiceNameServer = new(counterServer)
)

func main() {
    go func() {
        lis, err := net.Listen("tcp", ":50551")
        if err != nil {
            log.Fatal(err)
        }
        defer lis.Close()

        grpcServer := grpc.NewServer()

        counter.RegisterCounterSomeServiceNameServer(grpcServer, mainServer)

        if err := grpcServer.Serve(lis); err != nil {
            log.Fatal(err)
        }
    }()

    http.Handle("/", http.FileServer(http.Dir("./public")))

    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal(err)
    }
}

Зробимо ініціалізацію Golang-проєкту й запустимо сервер:

go mod init
go run main.go
~/go/src/gitlab.com/go-yp/grpc-counter
├── go.mod  [+]
├── go.sum  [+]
├── main.go [+]
├── Makefile
├── protos
│   └── services
│       └── counter
│           └── counter.proto
└── models
    └── protos
        └── services
            └── counter
                └── counter.pb.go

gRPC-сервер чекає даних, переданих протоколом HTTP/2, а JavaScript-клієнт у браузері (який ми згенеруємо далі) передає дані протоколом HTTP 1.1; відповідно потрібен проксі, що зможе перетворити один протокол на інший.

Рекомендованим вирішенням є Envoy Proxy, про Envoy можна почитати в DevOps дайджесті або послухати доповідь Envoy as TCP proxy Олега Миколайченка.

Я хотів зробити клієнт-серверну взаємодію напряму — тому знайшов вирішення, як це здійснити, у статті Proxy gRPC-Web directly in your Go Server.

package main

import (
    "context"
    "gitlab.com/go-yp/grpc-counter/models/protos/services/counter"
    "golang.org/x/net/http2"
    "golang.org/x/net/http2/h2c"
    "log"
    "net/http"
    "sync/atomic"

    "github.com/improbable-eng/grpc-web/go/grpcweb"
    "google.golang.org/grpc"
)

type counterServer struct {
    count uint32
}

func (s *counterServer) CountSomeMethodName(context.Context, *counter.Empty) (*counter.Response, error) {
    var newCount = atomic.AddUint32(&s.count, 1)

    return &counter.Response{
        Count: newCount,
    }, nil
}

var (
    mainServer counter.CounterSomeServiceNameServer = new(counterServer)
)

func main() {
    go func() {
        grpcServer := grpc.NewServer()
        grpcWebServer := grpcweb.WrapServer(grpcServer)

        counter.RegisterCounterSomeServiceNameServer(grpcServer, mainServer)

        var handler = h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            w.Header().Set("Access-Control-Allow-Origin", "*")
            w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
            w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-User-Agent, X-Grpc-Web")

            grpcWebServer.ServeHTTP(w, r)
        }), new(http2.Server))

        err := http.ListenAndServe(":50551", handler)
        if err != nil {
            log.Fatal(err)
        }
    }()

    http.Handle("/", http.FileServer(http.Dir("./public")))

    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal(err)
    }
}

gRPC-сервер успішно запускається, тепер залишилося згенерувати й під’єднати клієнт у браузері:

proto-client:
    mkdir -p ./client
    protoc -I . protos/services/counter/*.proto --js_out=import_style=commonjs,binary:client --grpc-web_out=import_style=commonjs,mode=grpcwebtext:client

make proto-client

~/go/src/gitlab.com/go-yp/grpc-counter
├── client
│   ├── app.js
│   └── protos
│       └── services
│           └── counter
│               ├── counter_grpc_web_pb.js [+]
│               └── counter_pb.js          [+]
├── go.mod
├── go.sum
├── main.go
├── Makefile
├── protos
│   └── services
│       └── counter
│           └── counter.proto
└── models
    └── protos
        └── services
            └── counter
                └── counter.pb.go

У файлі counter_pb.js будуть моделі та службові обгортки:

// source: protos/services/counter/counter.proto
// GENERATED CODE -- DO NOT EDIT!

var jspb = require('google-protobuf');

// ...

proto.counter.Empty = function(opt_data) {
  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};

// ...

proto.counter.Response = function(opt_data) {
  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};

// ...

У файлі counter_grpc_web_pb.js буде gRPC-клієнт:

// GENERATED CODE -- DO NOT EDIT!

const grpc = {};
grpc.web = require('grpc-web');

const proto = {};
proto.counter = require('./counter_pb.js');

// ...

proto.counter.CounterSomeServiceNameClient = function(hostname, credentials, options) {
  if (!options) options = {};
  options['format'] = 'text';

  this.client_ = new grpc.web.GrpcWebClientBase(options);

  this.hostname_ = hostname;
};

// ...

Цього досить, щоб зробити реалізацію в app.js, схожу на попередній AJAX-приклад:

const {Empty, Response} = require("./protos/services/counter/counter_pb");
const {CounterSomeServiceNameClient} = require("./protos/services/counter/counter_grpc_web_pb");

const app = new CounterSomeServiceNameClient("http://localhost:50551");

const request = new Empty();

app.countSomeMethodName(request, {}, (err, response) => {
    if (err) {
        console.error(err);

        return;
    }

    /** @type Response response */

    document.getElementById("js-counter").innerHTML = response.getCount();
});

І останні приготування package.json і webpack.config.js, щоб зібрати клієнтську частину:

{
  "name": "grpc-counter-example",
  "version": "0.1.0",
  "description": "gRPC counter example",
  "license": "MIT",
  "dependencies": {
    "grpc-web": "^1.0.0",
    "google-protobuf": "^3.6.1"
  },
  "devDependencies": {
    "@grpc/proto-loader": "^0.5.4",
    "webpack": "^4.16.5",
    "webpack-cli": "^3.1.0"
  },
  "scripts": {
    "build": "webpack --mode production"
  }
}
module.exports = {
    context: __dirname,

    entry: {
        app: './client/app'
    },

    output: {
        path: __dirname + '/public/js',
        filename: '[name].js'
    },
};
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>gRPC counter example</title>
</head>
<body>
    <p>
        The page was viewed <span id="js-counter">0</span> times
    </p>
    <script src="/js/app.js"></script>
</body>
</html>
~/go/src/gitlab.com/go-yp/grpc-counter
├── [4.0K]  client
│   ├── [ 514]  app.js
│   └── [4.0K]  protos
│       └── [4.0K]  services
│           ├── [4.0K]  counter
│           │   ├── [3.5K]  counter_grpc_web_pb.js
│           │   └── [8.4K]  counter_pb.js
├── [ 386]  go.mod
├── [5.8K]  go.sum
├── [1.4K]  main.go
├── [ 887]  Makefile
├── [4.0K]  models
│   └── [4.0K]  protos
│       └── [4.0K]  services
│           └── [4.0K]  counter
│               └── [7.0K]  counter.pb.go
├── [ 382]  package.json
├── [4.0K]  protos
│   └── [4.0K]  services
│       ├── [4.0K]  counter
│       │   └── [ 187]  counter.proto
├── [4.0K]  public
│   ├── [ 257]  index.html
│   └── [4.0K]  js
│       ├── [282K]  app.js
└── [ 186]  webpack.config.js
npm i
npm run build

go run main

browse http://localhost:8080/

Готовий приклад можна побачити в репозиторії.

Серед мінусів — розмір app.js ~ 282 KB, у якому підключені лише gRPC- і protobuf-бібліотеки.

gRPC-прототип структури форуму

Зробімо трохи складніший приклад, щоб подивитися, який буде розмір app.js.

Завдання: треба зробити простий форум зі створенням теми, коментарями та модерацією.

Підготуємо proto-файл, що описує методи:

syntax = "proto3";

package forum;

import "protos/services/forum/topic.proto";

service AnonymousForum {
  rpc CreateTopic (CreateTopicRequest) returns (UpdateTopicResponse);
  rpc UpdateTopic (UpdateTopicRequest) returns (UpdateTopicResponse);
  rpc TopicList (Empty) returns (TopicListResponse);

  rpc AddComment (AddCommentRequest) returns (Empty);
  rpc Topic (TopicRequest) returns (FullTopicResponse);
}

service ModerationForum {
  rpc TopicList(Empty) returns (TopicListResponse);
  rpc TopicApprove(TopicRequest) returns (Empty);
  rpc TopicReject(TopicRequest) returns (Empty);

  rpc CommentList(Empty) returns (CommentListResponse);
  rpc CommentApprove(CommentRequest) returns (Empty);
  rpc CommentReject(CommentRequest) returns (Empty);
}

А такий вигляд матимуть моделі:

syntax = "proto3";

package forum;

message Empty {}

message UpdateTopicResponse {
  bool success = 1;
  string error_message = 2;
}

message CreateTopicRequest {
  string title = 1;
  string description = 2;
}

message UpdateTopicRequest {
  string id = 1;
  string title = 2;
  string description = 3;
}

message Topic {
  string id = 1;
  string title = 2;
  string description = 3;
}

message TopicListResponse {
  repeated Topic items = 1;
}

message TopicRequest {
  string id = 1;
}

message AddCommentRequest {
  string topic_id = 1;
  string username = 2;
  string text = 3;
}

message Comment {
  string id = 1;
  string username = 2;
  string text = 3;
}

message CommentListResponse {
  repeated Comment items = 1;
}

message CommentRequest {
  string id = 1;
}

message FullTopicResponse {
  string id = 1;
  string title = 2;
  string description = 3;
  repeated Comment items = 4;
}
└── protos
    └── services
        └── forum
            ├── anonymous.proto
            └── topic.proto

За аналогією з gRPC-лічильником згенерую клієнт:

protoc -I . protos/services/forum/*.proto --js_out=import_style=commonjs,binary:client --grpc-web_out=import_style=commonjs,mode=grpcwebtext:client

Під’єднаємо згенерований клієнт і використаємо його в прикладі:

const {CreateTopicRequest, UpdateTopicResponse} = require("./protos/services/forum/topic_pb");
const {AnonymousForum} = require("./protos/services/forum/anonymous_grpc_web_pb");

const app = new AnonymousForum("http://localhost:50551");

const request = new CreateTopicRequest();
request
    .setTitle("gRPC forum example")
    .setDescription("gRPC forum example");

app.addTopic(request, {}, (err, response) => {
    if (err) {
        console.error(err);

        return;
    }

    /** @type UpdateTopicResponse response */

    console.log(response);
});
├── [4.0K]  client
│   ├── [ 514]  app.js
│   └── [4.0K]  protos
│       └── [4.0K]  services
│           └── [4.0K]  forum
│               ├── [ 27K]  anonymous_grpc_web_pb.js
│               ├── [ 530]  anonymous_pb.js
│               └── [ 65K]  topic_pb.js
├── [1.0K]  Makefile
├── [ 382]  package.json
├── [4.0K]  protos
│   └── [4.0K]  services
│       └── [4.0K]  forum
│           ├── [ 755]  anonymous.proto
│           └── [ 898]  topic.proto
├── [4.0K]  public
│   └── [4.0K]  js
│       └── [310K]  forum-app.js
└── [ 229]  webpack.config.js

Бачимо, що розмір forum-app.js ~ 310 KB.

Далі буде

У цій статті я планував створити прототип форуму DOU зі збереженням у MongoDB, але стаття і так вийшла об’ємною; якщо сподобалася, то зроблю продовження.

Епілог

Одна із цілей написання — це те, що мені тема цікава, а шукаючи, бачив мало повноцінних прикладів; тому буду радий зустріти посилання на гарні приклади в коментарях.

Пишучи статтю, я зрозумів, що gRPC web добре підходить для розробки нових і складних проєктів.

Якщо прочитавши статтю, ви захотіли перевести вже наявну мікросервісну взаємодію з REST на gRPC, щоб було простіше розробляти нові методи й сервіси, то так, це доцільно.

Чи переводити вже наявну REST-взаємодію браузера й сервера на gRPC web? Ліпше подумайте, а чи справді вам це треба.

У майбутньому хочу писати кращі статті, тому буду радий коментарям про те, як і що міг би пояснити простіше.

Похожие статьи:
У випуску: інформація та документація від Apple, продвинута аппа для генераціі скріншотів, найбільш касові аплікації App Store, що змінилося...
Оперативники Департаменту кіберполіції разом зі слідчими Нацполіції провели багаторівневу спецоперацію та викрили групу хакерів,...
Клим Протасов — 3D-аниматор из Харькова. Участвовал в создании таких фильмов и сериалов, как «Игра престолов», «Маугли»,...
В Україні зараз є 307 тисяч ІТ-спеціалістів, але заброньовано — не більше, аніж 2 тисячі з них, про це під час інтервʼю...
Компания Яндекс сообщила, что её Яндекс.Браузер начал предупреждать о веб-страницах, которые подписывают...
Яндекс.Метрика