Created
August 23, 2018 15:34
-
-
Save SlootSantos/10de92d97d1b0b0596b9489519afc186 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const server = require('express')(); | |
const axios = require('axios'); | |
const createClient = require('../client'); | |
// very same thing as in databaseService.js | |
const serviceDefinition = { | |
name: 'anyService', | |
ipv4: '127.0.0.1', | |
port: 3001 | |
}; | |
// create a new map to store the locations we care about | |
const serviceLocations = new Map(); | |
// first start the server and | |
// then register at the discovery service | |
// then fetch the database service and store it! | |
server | |
.get('/', async (_, res) => { | |
const dbService = serviceLocations.get('databaseService'); | |
const response = await axios.get(`http://${dbService.ipv4}:${dbService.port}/db`); | |
res.send(response.data); | |
}) | |
.listen(serviceDefinition.port, () => console.log(`AnyService listens on port ${serviceDefinition.port}`)); | |
(async () => { | |
const client = await createClient(); | |
await client.register(serviceDefinition); | |
const { registrations: [dbService] } = await client.fetchServiceLocation({ | |
registrations: [{ | |
name: 'databaseService' | |
}] | |
}); | |
serviceLocations.set('databaseService', dbService); | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const REGISTRY_PROTO_PATH = `${__dirname}/protos/registry.proto`; | |
const { promisify } = require('util'); | |
const grpc = require('grpc'); | |
const protoLoader = require('@grpc/proto-loader'); | |
const packageDefinition = protoLoader.loadSync(REGISTRY_PROTO_PATH); | |
const { registry: registryProto } = grpc.loadPackageDefinition(packageDefinition); | |
module.exports = async () => { | |
const client = new registryProto.Registry('localhost:3333', grpc.credentials.createInsecure()); | |
const promisableFns = ['register', 'unregister', 'fetchServiceLocation']; | |
// I wrapped the client instance in a proxy because... | |
// I can. | |
// I wanted to use promise, and because I didn't want to | |
// write an astonishing number of 4 lines to promisify all of those functions | |
// Looks kinda sexy to use a proxy.. doesn't it? | |
// WARNING: though it looks sexy, please be aware that we are creating a new | |
// function instance on every property look up. | |
// "promisify(target[property])" creates a new function everytime! | |
const clientProxy = new Proxy(client, { | |
get: (target, property) => { | |
return promisableFns.includes(property) | |
? promisify(target[property]) | |
: target[property]; | |
} | |
}); | |
return clientProxy; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const server = require('express')(); | |
const createClient = require('../client'); | |
// well.. this is on our localmachine | |
// hence we know for sure where what location this process has. | |
// in production you'd want to come up w/ something more flexible | |
const serviceDefinition = { | |
name: 'databaseService', | |
ipv4: '127.0.0.1', | |
port: 3000 | |
}; | |
// just a example response that our db could return | |
const exampleResponse = { | |
entries: [{ | |
name: 'what', | |
value: 'ever you like' | |
}, | |
{ | |
name: 'what', | |
value: 'ever you like' | |
}] | |
}; | |
// first start the server and | |
// then register at the discovery service | |
server | |
.get('/db', (_, res) => { | |
res.send(exampleResponse); | |
}) | |
.listen(serviceDefinition.port, () => console.log(`DB service listens on ${serviceDefinition.port}`)); | |
(async () => { | |
const client = await createClient(); | |
const response = await client.register(serviceDefinition); | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// we need to tell the compiler which version we're using | |
syntax = "proto3"; | |
package registry; | |
// here we define the service and methods we're going to use later on | |
// to do the registration process | |
service Registry { | |
rpc Register (Registration) returns (RegisterResponse); | |
rpc Unregister (Registration) returns (RegisterResponse); | |
rpc fetchServiceLocation (RegistrationFetchRequest) returns (RegistrationList); | |
} | |
// we define what a registration needs to have | |
// all optional and self explanatory | |
message Registration { | |
optional string name = 1; | |
optional string ipv4 = 2; | |
optional string port = 3; | |
optional string domain = 4; | |
} | |
// a list/array of registrations | |
message RegistrationList { | |
repeated Registration registrations = 1; | |
} | |
// w/ this message we define what the server needs | |
// to know what you want to have | |
message RegistrationFetchRequest { | |
repeated Registration registrations = 1; | |
optional bool fetchAll = 2; | |
} | |
// simple response when you sign up | |
message RegisterResponse { | |
required string message = 1; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// let's create a variable for the path of the .proto file | |
const REGISTRY_PROTO_PATH = `${__dirname}/protos/registry.proto`; | |
// import grpc and the loader | |
const grpc = require('grpc'); | |
const protoLoader = require('@grpc/proto-loader'); | |
// here we actually load the proto content | |
const packageDefinition = protoLoader.loadSync(REGISTRY_PROTO_PATH); | |
// and then we create the definition of it | |
// so that we can go ahead and use it | |
const { registry: registryProto } = grpc.loadPackageDefinition(packageDefinition); | |
// we use Map because.. well why not. It's a perfect use case for a Map | |
const registryStore = new Map(); | |
// to save a bunch of boilerplate code | |
// we create a "higher order function" | |
// that takes a function which will define what to do w/ the request | |
// aka put it into the map, remove it from there, return all entries, etc. | |
const registryFunction = mapOperatorFn => ({ request }, rpcCallback) => { | |
let error; | |
let payload; | |
try { | |
payload = mapOperatorFn(request); | |
} catch (e) { | |
error = e; | |
} | |
rpcCallback(error, payload); | |
}; | |
// here we simply create the operator function and pass it | |
// into our registryFunction wrapper | |
// we set the properties for every given service and identify them by their name | |
// note: even if there is an entry already we simply override it | |
// since we believe that the latest registration is always the correct one | |
const register = registryFunction(({ name, ipv4, port }) => { | |
registryStore.set(name, { ipv4, port }); | |
return { | |
message: `${name} was registered under ${ipv4}:${port}` | |
}; | |
}); | |
// this function simply deletes the registration from the map | |
const unregister = registryFunction(({ name }) => { | |
registryStore.delete(name); | |
return { | |
message: `${name} was unregistered` | |
}; | |
}); | |
// this function let's you fetch one or more registered services | |
// e.g.: if you care about the database-services location | |
// you'd just send an array of locations you're interested in: ['databaseService'] | |
// BE AWARE that looping through ALL given services in the Map AND then looking | |
// in the array whether it's what you requested could give you | |
// a performance hit w/ big datasets. | |
// Map look ups are very efficient => O(1) | |
// vs. the looping through that we're doing => O(n) + O(n) = O(n^2) | |
const fetchServiceLocation = registryFunction(({ registrations: requestedRegistrations = [], fetchAll }) => { | |
const reqRegistrationNames = [...requestedRegistrations].map(({ name }) => name); | |
const registrations = []; | |
for ([name, properties] of registryStore.entries()) { | |
if (reqRegistrationNames.includes(name) || fetchAll) { | |
registrations.push({ | |
name, | |
...properties | |
}) | |
} | |
} | |
return { registrations }; | |
}); | |
// in this iife we simply start the grpc server and define the services | |
// to match our .proto definition | |
(() => { | |
const server = new grpc.Server(); | |
server.addService(registryProto.Registry.service, { | |
register, | |
unregister, | |
fetchServiceLocation | |
}); | |
server.bind('0.0.0.0:3333', grpc.ServerCredentials.createInsecure()); | |
server.start(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment