Skip to content

Commit

Permalink
refactor(grpc): support multiple packages (breaking changes)
Browse files Browse the repository at this point in the history
  • Loading branch information
zry656565 committed Mar 18, 2019
1 parent 55646f5 commit d2ee6c9
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 47 deletions.
53 changes: 36 additions & 17 deletions packages/microservices/client/client-grpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ let grpcProtoLoaderPackage: any = {};
export class ClientGrpcProxy extends ClientProxy implements ClientGrpc {
protected readonly logger = new Logger(ClientProxy.name);
protected readonly url: string;
protected grpcClient: any;
protected grpcClientMap: { [key: string]: any };

constructor(protected readonly options: ClientOptions['options']) {
super();
Expand All @@ -32,23 +32,36 @@ export class ClientGrpcProxy extends ClientProxy implements ClientGrpc {
require('grpc'),
);
grpcProtoLoaderPackage = loadPackage(protoLoader, ClientGrpcProxy.name);
this.grpcClient = this.createClient();
this.grpcClientMap = this.createClients();
}

public getService<T extends {}>(name: string): T {
public getService<T extends {}>(name: string, pkgName?: string): T {
const options: any = isObject(this.options)
? { ...this.options, loader: '' }
: {};

if (!this.grpcClient[name]) {
let candidateClient: any = null;
let TargetClient: any = null;
if (pkgName) {
candidateClient = this.grpcClientMap[pkgName];
TargetClient = candidateClient && candidateClient[name];
} else {
for (const instance of Object.values(this.grpcClientMap)) {
if (instance[name]) {
TargetClient = instance[name];
break;
}
}
}
if (!TargetClient) {
throw new InvalidGrpcServiceException();
}
const grpcClient = new this.grpcClient[name](
const grpcClient = new TargetClient(
this.url,
options.credentials || grpcPackage.credentials.createInsecure(),
options,
);
const protoMethods = Object.keys(this.grpcClient[name].prototype);
const protoMethods = Object.keys(TargetClient.prototype);
const grpcService = {} as T;
protoMethods.forEach(m => {
const key = m[0].toLowerCase() + m.slice(1, m.length);
Expand Down Expand Up @@ -117,19 +130,23 @@ export class ClientGrpcProxy extends ClientProxy implements ClientGrpc {
};
}

public createClient(): any {
public createClients(): any {
const grpcContext = this.loadProto();
const packageName = this.getOptionsProp<GrpcOptions>(
const packageNames = this.getOptionsProp<GrpcOptions>(
this.options,
'package',
'packages',
);
const grpcPkg = this.lookupPackage(grpcContext, packageName);
if (!grpcPkg) {
const invalidPackageError = new InvalidGrpcPackageException();
this.logger.error(invalidPackageError.message, invalidPackageError.stack);
throw invalidPackageError;
const grpcPkgs: { [key: string]: any } = {};
for (const pkgName of packageNames) {
const grpcPkg = this.lookupPackage(grpcContext, pkgName);
if (!grpcPkg) {
const invalidPackageError = new InvalidGrpcPackageException();
this.logger.error(invalidPackageError.message, invalidPackageError.stack);
throw invalidPackageError;
}
grpcPkgs[pkgName] = grpcPkg;
}
return grpcPkg;
return grpcPkgs;
}

public loadProto(): any {
Expand Down Expand Up @@ -162,8 +179,10 @@ export class ClientGrpcProxy extends ClientProxy implements ClientGrpc {
}

public close() {
this.grpcClient && this.grpcClient.close();
this.grpcClient = null;
if (this.grpcClientMap) {
Object.values(this.grpcClientMap).forEach((client) => client.close());
}
this.grpcClientMap = null;
}

public async connect(): Promise<any> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface GrpcOptions {
maxReceiveMessageLength?: number;
credentials?: any;
protoPath: string;
package: string;
packages: string[];
protoLoader?: string;
loader?: {
keepCase?: boolean;
Expand Down
37 changes: 20 additions & 17 deletions packages/microservices/server/server-grpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,26 +58,29 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {

public async bindEvents() {
const grpcContext = this.loadProto();
const packageName = this.getOptionsProp<GrpcOptions>(
const packageNames: string[] = this.getOptionsProp<GrpcOptions>(
this.options,
'package',
'packages',
);
const grpcPkg = this.lookupPackage(grpcContext, packageName);
if (!grpcPkg) {
const invalidPackageError = new InvalidGrpcPackageException();
this.logger.error(invalidPackageError.message, invalidPackageError.stack);
throw invalidPackageError;
}

// Take all of the services defined in grpcPkg and assign them to
// method handlers defined in Controllers
for (const definition of this.getServiceNames(grpcPkg)) {
this.grpcClient.addService(
// First parameter requires exact service definition from proto
definition.service.service,
// Here full proto definition required along with namespaced pattern name
await this.createService(definition.service, definition.name),
);
for (const packageName of packageNames) {
const grpcPkg = this.lookupPackage(grpcContext, packageName);
if (!grpcPkg) {
const invalidPackageError = new InvalidGrpcPackageException();
this.logger.error(invalidPackageError.message, invalidPackageError.stack);
throw invalidPackageError;
}

// Take all of the services defined in grpcPkg and assign them to
// method handlers defined in Controllers
for (const definition of this.getServiceNames(grpcPkg)) {
this.grpcClient.addService(
// First parameter requires exact service definition from proto
definition.service.service,
// Here full proto definition required along with namespaced pattern name
await this.createService(definition.service, definition.name),
);
}
}
}

Expand Down
60 changes: 51 additions & 9 deletions packages/microservices/test/client/client-grpc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,75 @@ class GrpcService {
test = null;
}

class AnotherGrpcService {
imported = null;
}

describe('ClientGrpcProxy', () => {
let client: ClientGrpcProxy;
let clientWithMultiplePackages: ClientGrpcProxy;

beforeEach(() => {
client = new ClientGrpcProxy({
protoPath: join(__dirname, './test.proto'),
package: 'test',
packages: ['test'],
});
clientWithMultiplePackages = new ClientGrpcProxy({
protoPath: join(__dirname, './test.proto'),
packages: ['test', 'imported'],
});
});

describe('getService', () => {
describe('when "grpcClient[name]" is nil', () => {
describe('when "grpcClientMap[name]" is nil', () => {
it('should throw "InvalidGrpcServiceException"', () => {
(client as any).grpcClient = {};
(client as any).grpcClientMap = {};
expect(() => client.getService('test')).to.throw(
InvalidGrpcServiceException,
);
});
});
describe('when "grpcClient[name]" is not nil', () => {
describe('when "grpcClientMap[name]" is not nil', () => {
it('should create grpcService', () => {
(client as any).grpcClient = {
test: GrpcService,
const grpcClient = { test: GrpcService };
(client as any).grpcClientMap = {
test: grpcClient
};
expect(() => client.getService('test')).to.not.throw(
InvalidGrpcServiceException,
);
});
});
describe('when there\'re several packages in "grpcClientMap"', () => {
it('should create several grpcServices', () => {
const grpcClient = { testService: GrpcService };
const grpcAnotherClient = { anotherService: AnotherGrpcService };
(client as any).grpcClientMap = {
testPkg: grpcClient,
importedPkg: grpcAnotherClient,
};
expect(() => client.getService('testService')).to.not.throw(
InvalidGrpcServiceException,
);
expect(() => client.getService('anotherService')).to.not.throw(
InvalidGrpcServiceException,
);
});
it('should only get services in matched packages through getService()', () => {
const grpcClient = { testService: GrpcService };
const grpcAnotherClient = { anotherService: AnotherGrpcService };
(client as any).grpcClientMap = {
testPkg: grpcClient,
importedPkg: grpcAnotherClient,
};
expect(() => client.getService('testService', 'importedPkg')).to.throw(
InvalidGrpcServiceException,
);
expect(() => client.getService('anotherService', 'testPkg')).to.throw(
InvalidGrpcServiceException,
);
});
});
});

describe('createServiceMethod', () => {
Expand Down Expand Up @@ -188,14 +228,14 @@ describe('ClientGrpcProxy', () => {
});
});

describe('createClient', () => {
describe('createClients', () => {
describe('when package does not exist', () => {
it('should throw "InvalidGrpcPackageException"', () => {
sinon.stub(client, 'lookupPackage').callsFake(() => null);
(client as any).logger = new NoopLogger();

try {
client.createClient();
client.createClients();
} catch (err) {
expect(err).to.be.instanceof(InvalidGrpcPackageException);
}
Expand All @@ -219,7 +259,9 @@ describe('ClientGrpcProxy', () => {
describe('close', () => {
it('should call "close" method', () => {
const grpcClient = { close: sinon.spy() };
(client as any).grpcClient = grpcClient;
(client as any).grpcClientMap = {
test: grpcClient,
};

client.close();
expect(grpcClient.close.called).to.be.true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('ClientProxyFactory', () => {
transport: Transport.GRPC,
options: {
protoPath: join(__dirname, './test.proto'),
package: 'test'
packages: ['test'],
},
});
expect(proxy instanceof ClientGrpcProxy).to.be.true;
Expand Down
7 changes: 7 additions & 0 deletions packages/microservices/test/client/imported.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
syntax = "proto3";

package imported;

service AnotherService {

}
2 changes: 2 additions & 0 deletions packages/microservices/test/client/test.proto
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
syntax = "proto3";

import public "imported.proto";

package test;

service TestService {
Expand Down
3 changes: 3 additions & 0 deletions packages/microservices/test/server/imported.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
syntax = "proto3";

package imported;
2 changes: 1 addition & 1 deletion packages/microservices/test/server/server-factory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ describe('ServerFactory', () => {
expect(
ServerFactory.create({
transport: Transport.GRPC,
options: { protoPath: '', package: '' },
options: { protoPath: '', packages: [''] },
}) instanceof ServerGrpc,
).to.be.true;
});
Expand Down
51 changes: 50 additions & 1 deletion packages/microservices/test/server/server-grpc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ class NoopLogger extends Logger {

describe('ServerGrpc', () => {
let server: ServerGrpc;
let serverWithMultiplePackages: ServerGrpc;
beforeEach(() => {
server = new ServerGrpc({
protoPath: join(__dirname, './test.proto'),
package: 'test',
packages: ['test'],
} as any);
serverWithMultiplePackages = new ServerGrpc({
protoPath: join(__dirname, './test.proto'),
packages: ['test', 'imported'],
} as any);
});

Expand Down Expand Up @@ -85,6 +90,50 @@ describe('ServerGrpc', () => {
expect((server as any).grpcClient.addService.calledTwice).to.be.true;
});
});
describe('when several packages exist', () => {
it('should call "addService"', async () => {
const serverWMP = serverWithMultiplePackages;
// load first package
sinon.stub(serverWMP, 'lookupPackage').callsFake((_, pkgName) => {
const pkgMap = {
test: {
test: { service: true },
test2: { service: true },
},
imported: {
anotherTest: { service: true },
}
};
return pkgMap[pkgName];
});
sinon.stub(serverWMP, 'getServiceNames').callsFake((grpcPkg) => {
if (grpcPkg.test) {
return [
{
name: 'test',
service: true,
},
{
name: 'test2',
service: true,
},
];
} else {
return [
{
name: 'anotherTest',
service: true,
},
];
}
});

(serverWMP as any).grpcClient = { addService: sinon.spy() };

await serverWMP.bindEvents();
expect((serverWMP as any).grpcClient.addService.callCount).to.be.equal(3);
});
});
});

describe('getServiceNames', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/microservices/test/server/test.proto
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
syntax = "proto3";

import public "imported.proto";

package test;

0 comments on commit d2ee6c9

Please sign in to comment.