задний план
В последнее время в процессе развития бизнеса обнаруживается, что поддержание деловой связи между несколькими службами одновременно требует больших затрат на разработку. Изучив код некоторых моих коллег, я обнаружил, что они обычно использовалиrpc
для создания службы, поэтому я решил попробовать использоватьgRPC
Рефакторинг сервиса. Вот некоторые основы проекта:
- Поддерживайте несколько независимых сервисов одновременно.
- Службы развертываются отдельно на нескольких машинах.
- Между сервисами есть деловая связь (
http
). - каталог проекта
│ ├── controller
│ ├── service
│ ├── route
├── app.ts
анализ проблемы
Всякий раз, когда я пытаюсь добавить новую службу, мне нужно маршрутизировать, чтобы убедиться, что тип поля и имя точно совпадают, мне также нужно написатьAPI
Документируйте ограничения, а затем нужно написать дополнительный код для ввода и вывода формата данных. Наконец, при приеме изAPI
ответ, служба снова выполнит весь процесс в обратном порядке. Весь этот процесс выглядит так:Развитие идет медленно.
анализ спроса
- Рефакторинг проводится с целью максимально не разрушить исходную структуру проекта и бизнес-код.
- Напиши
API
Вызовы нескольких служб могут быть ограничены одновременно. - Не нужно писать дополнительный код для ввода и вывода.
Процесс реализации
здесь сserver
конец как пример. Во-первых, используйтеgRPC
Замените запись сервисной программы.
import service from 'controller';
// 加载proto文件
const grpcPkg = protoLoader.loadSync(file, loader);
function main() {
const server = new grpc.Server();
// 遍历grpcPkg,加载对应的service method
bindServices(server, service, grpcPkg);
server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
server.start();
}
согласно сrpc
изФундаментальный, нам нужно написать.proto
файл для описания воздействия службыapi
:
syntax = "proto3";
package test;
service Api {
rpc index (Query) returns (Response) {};
rpc show (Param) returns (Response) {};
rpc update (Body) returns (Response) {};
rpc create (Body) returns (Response) {};
rpc del (Param) returns (Response) {};
}
message Response {
int32 code = 1;
string data = 2;
}
Вот делаю файлservice
Заявленный метод такой же, какhttp
Услугиcontroller
быть последовательным
class Api {
public index() {}
public show() {}
public update() {}
public create() {}
public del() {}
}
существуетbindService
, мы можем перебрать загруженныйgrpcPkg
и заявить об этомservice
а такжеcontroller
Сопоставьте их один за другим.
// 初始化方法
for (const definition of getServiceNames(grpcPkg)) {
const service = (service as any)[definition.name.toLowerCase()];
!!service &&
service.addService(
// proto file中解析出的service
definition.service.service,
// 包装对应service
await createService(service)
);
}
для того, чтобы гарантировать, что каждыйcontroller
Метод может точно иgrpcPkg
Загруженные методы должны соответствовать и могут использоваться во всехcontroller
В общем, избегая конфликта одновременных методов, мы можем использоватьreflect-metadata
собиратьcontroller
метод, чтобы максимально избежать вторжения в первоначальный бизнес.
// decorator.ts
export const ROUTE_MAPPING_METADATA = 'MODULE_ROUTE_MAPPING';
export const ROUTE_METADATA = 'MODULE_ROUTE';
export const ROUTE_METADATA_PARAMS = 'MODULE_ROUTE_PARAMS';
export function Handler(params: any = {}) {
return (target: any, methodName: string, descriptor: PropertyDescriptor) => {
// 收集方法名
const methods = Reflect.getMetadata(ROUTE_MAPPING_METADATA, target) || new Set<string>();
methods.add(methodName);
// 收集方法引用
Reflect.defineMetadata(ROUTE_METADATA, descriptor.value, target, methodName);
// 收集方法额外参数
Reflect.defineMetadata(ROUTE_MAPPING_METADATA, methods, target);
Reflect.defineMetadata(
ROUTE_METADATA_PARAMS,
params,
target,
methodName
);
return descriptor;
};
}
// controller.ts
class Api {
// rpc method
@Handler()
public index() {}
// http method
public show() {}
}
затем вaddService
, мы можем получитьgrpcPkg
соответствующий методу вmethod
, и это упаковка. Для достижения цели единого ввода и вывода.
function createService(controller: any) {
const handlers: any = {};
// 获取当前controller的方法集
const methods: Set<string> = Reflect.getMetadata(ROUTE_MAPPING_METADATA, controller);
for (const method of Array.from(methods)) {
const handler = Reflect.getMetadata(ROUTE_METADATA, controller, method);
if (!handler) continue;
// 包装method
const enhancer = createServiceMethod(
handler
);
handlers[method] = enhancer;
}
// 返回方法集
return handlers;
}
Ну наконец тоproto
Проблемы с обслуживанием файлов, так как несколько служб могут использовать один и тот жеproto
документ. Здесь мы можем использоватьgit subtree
, извлеките proto как подпроект для обслуживания.