How to tackle the problem of naming in high-volume environments
Originally posted here
Introduction
If you've ever created a project with Google Cloud or Docker you might have noticed every new project gets a unique, randomly-generated name. This is an effective way to avoid naming collisions, with the added benefit of being able to easily track your projects. Let's look at how to create a scalable naming system like these, and how to put it to use in a few different ways.
Getting started
The first thing we need to do is initialize our Node.js application. To do this quickly, lets run npm init -y
in our project directory. This will create a generic package.json file for us, and we can edit it to our liking later.
Setting up the file structure
When creating an application intended to scale, modularity is key. Organization is also important as other developers could eventually be working on your projects. Our file structure will look something like this:
├── package.json
├── src/
|- index.js
|- lists/
|- |- nouns.js
|- |- adverbs.js
Our word lists
I've created some pretty lengthy lists of adverbs and nouns for you to use. You can find them here, or create your own lists if you'd like.
Make use of the lists
We need to update our application to import our lists, update the following code:
src/index.js
const adverbs = require("./lists/adverbs.js");
const nouns = require("./lists/nouns.js");
Now that they're imported, we can use them in our application. For now, lets just see how many words we have in each list. Add the following code:
src/index.js
console.log("Adverbs: ", adverbs.length);
console.log("Nouns: ", nouns.length);
If you used my lists, you should see something like this:
Adverbs: 710
Nouns: 1097
Randomize all the things
For the purposes of this application, random is a good thing, each element of our name should be random and unique. To return a random element from our list, we can use the following function expression:
src/index.js
const getRandomWord = (array) => {
const randomIndex = Math.floor(Math.random() * array.length);
return array[randomIndex];
};
We also need to create a random number, for now we'll stick with a 4 digit number. To do this, we can use the following function expression:
src/index.js
const getRandomNumber = () => {
let randomNumber = Math.floor(Math.random() * (9999 - 1000) + 1000);
return randomNumber;
};
Putting it all together
We need to create a function that will get a random word from each of our lists, and add a number to the end. First, we'll add the following:
src/index.js
const adverb = getRandomWord(adverbs);
const noun = getRandomWord(nouns);
const combineWords = (adverb, noun) => {
let result = `${adverb}-${noun}-${getRandomNumber()}`;
return result;
};
module.exports = console.log(combineWords(adverb, noun));
If we've done everything correctly, we should be able to call our function and get a random name. To test this, we can run our application in our terminal:
node src/index.js
and we should see something like this printed out:
chronologically-broken-4521
Voila! We've got a random name! Now we just need to make sure our application prevents collisions, if not, we could end up with the same name multiple times.
Keeping track of it
This is the most basic way to use our application, but we're not tracking what we've already used, so we'll need to add some logic to make sure we don't get the same name twice. For this, we'll need a datastore that is as scalable as our application. In this case, we'll use Redis, a very popular and scalable database, and ioRedis, a Node.js wrapper for Redis.
We'll need to initialize the redis client, to do so add the following code:
src/index.js
const redis = require("ioredis");
const client = new redis(process.env.REDIS_URL);
And then add a function which will check if a redis key with the value of our name exists. If it does, we'll need to generate a new name. Once a unique name is generated, we need to return the name. To do this, we'll need to add the following code:
src/index.js
async function getRandomName() {
const name = combineWords(adverb, noun);
const exists = await redis.exists(name);
if (exists !== 0) {
console.log("Cache hit, generating new name");
await redis.incr("duplicates");
return getRandomName();
}
await redis.set(name, name);
return name;
}
if you've been following to this point, your src/index.js
file should look something like this:
const Redis = require("ioredis");
const redisUri = process.env.REDIS_URI;
const redis = new Redis(redisUri);
const adverbs = require("./lists/adverbs");
const nouns = require("./lists/nouns");
const getRandomWord = (array) => {
const randomIndex = Math.floor(Math.random() * array.length);
return array[randomIndex];
};
const adverb = getRandomWord(adverbs);
const noun = getRandomWord(nouns);
const randomNumber = () => {
let randomNumber = Math.floor(Math.random() * (9999 - 1000) + 1000);
return randomNumber;
};
const combineWords = (adverb, noun) => {
let result = `${adverb}-${noun}-${randomNumber()}`;
return result;
};
async function getRandomName() {
const name = combineWords(adverb, noun);
const exists = await redis.exists(name);
if (exists !== 0) {
console.log("Cache hit, generating new name");
await redis.incr("duplicates");
return getRandomName();
}
await redis.set(name, name);
return name;
}
Deploying as a serverless function
In this case, we're going to use the gcloud command line tool to deploy our application as a serverless function to Google Cloud Functions. To accomplish this, we'll need to wrap our application in an asynchronous function expression. Update the following code:
src/index.js
const Redis = require("ioredis");
const redisUri = process.env.REDIS_URI;
exports.generateName = async (req, res) => {
const redis = new Redis(redisUri);
const adverbs = require("./lists/adverbs");
const nouns = require("./lists/nouns");
const getRandomWord = (array) => {
const randomIndex = Math.floor(Math.random() * array.length);
return array[randomIndex];
};
const adverb = getRandomWord(adverbs);
const noun = getRandomWord(nouns);
const randomNumber = () => {
let randomNumber = Math.floor(Math.random() * (9999 - 1000) + 1000);
return randomNumber;
};
const combineWords = (adverb, noun) => {
let result = `${adverb}-${noun}-${randomNumber()}`;
return result;
};
async function getRandomName() {
const name = combineWords(adverb, noun);
const exists = await redis.exists(name);
if (exists !== 0) {
console.log("Cache hit, generating new name");
await redis.incr("duplicates");
return getRandomName();
}
await redis.set(name, name);
return name;
}
const name = await getRandomName();
res.send(name);
};
This change allows our function to be used as a serverless function which can be utilized by a variety of mediums. Run the following command in your terminal to authenticate with gcloud and deploy the function:
NOTE You'll need to have a project created in Google Cloud before you can deploy your function. You'll also need the Cloud Build API enabled in your project.
gcloud auth login
gcloud config set project [YOUR_PROJECT_ID]
gcloud functions deploy generateName --runtime nodejs16 --trigger-http --allow-unauthenticated --set-env-vars REDIS_URI={YOUR_REDIS_URI}
This process will take a few minutes to complete, and once complete, there will be a great deal of information printed out to the console. We can see that our function is deployed and we can see the URL of the function. This URL can be used to call our function from a client, or directly from the browser for testing.
Putting it to use
In this case, we'll use the function to name projects locally utilizing a bash script, knowing we can reuse the same endpoint in later applications. In a new directory, create a file called new-project.sh
and add the following code:
new-project.sh
#!/bin/bash
NAME=$(curl -s {YOUR_FUNCTION_URL})
echo "Creating Directory $NAME"
makeDir(){
mkdir -p "$NAME" && cd "$NAME"
}
makeDir
To test our bash script, we can run the following command in our terminal:
. ./new-project.sh
If all goes well, we should see a new directory created with a random name from our function, and we should be in that directory ready to start working on our project. The last thing we need to do is create an alias for our bash script. To do this, we'll need to add the following code to our .bashrc
file:
.bashrc
alias new-project=". /path/to/new-project.sh"
This will enable us to call our bash script from the command line, no matter which directory we're in. To test our new alias, we can run the following command in our terminal:
new-project
Which should now output something like this:
Creating Directory consquentially-silent-8851
consquentially-silent-8851
Conclusion and next steps
This serverless function can go on to be used in numerous applications with ease, and solves a very real problem with minimal effort. Stay tuned for more on this topic and others in the future!
Top comments (0)