задний план
В последнее время в процессе развития бизнеса обнаруживается, что поддержание деловой связи между несколькими службами одновременно требует больших затрат на разработку. Изучив код некоторых моих коллег, я обнаружил, что они обычно использовали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 как подпроект для обслуживания.