Design
In order to ensure long-term scalability and maintainability, the code structure is of enormous importance. In the following, we show how such a code structure could look like.
The essential parts are a CommandHandler/CommandManager
, EventHandler/EventManager
, lots of Structures
in order to
code faster and Plugins
, where your different features will be, such as Commands
, DB Stuff
...
Code Structure
We recommend following structure for your code:
├index.js
├─Structures/
├─Managers/
├─events/
├─Plugins/
├── General/
│ ├── commands/
│ │ ├── ping.js
│ │ └── ...
├── Developer/
│ ├── commands/
│ │ ├── eval.js
│ │ └── ...
├─Util/
└── ...
The following explains why this structure is suitable. If you want to follow this guide further, you should create these folders.
In the Managers
folder the Managers will be added e.g. CommandManager.js
, EventManager.js
. Generally codes, which
manage the system.
While in the Structures
folder mainly classes are added like BaseCommand.js
, CommandResponse.js
, Embed.js
,
Components.js
, which make it easier to add methods to objects.
The events
folder will contain the event handlers such as messageCreate.js
, debug.js
Your many useful features and categories end up in the Plugins
folder, where they should be categorically divided into
many folders.
The Util
folder contains functions or classes that help you convert certain things, such as timestamps, into a
human-readable format.
CommandHandler & BaseCommand
The CommandHandler
is the main class of the bot, which will handle all the commands and the events received from
Discord.
The BaseCommand
is the base class of all commands, which will be extended with theCommandResponse
class.
Steps showed in the following Guide
- Loading commands from different plugins
- Deploying slash commands
- Handling
messageCreate
&interactionCreate
events - Command rate limit handling
- Handle
Interaction
&Message
commands with the same code - Validating user provided arguments
- Correct permission and error handling
- Hot reloading commands
- Creating message and interaction collectors
EventHandler
You probably realized that Discordeno does not use an EventEmitter
to fire the events, but your own event function is
fired.
There are ways to adapt to an EventEmitter
, but we decided against it for the following reasons:
- It's easy to create memory leaks, when you add too many listeners or go carelessly with it.
- Many fragmented parts of event code complicate maintenance.
- ErrorHandling is difficult and debugging is harder when many listeners are open for the same events.
Structures
Structures are essential to abstract larger parts of code in smaller ready-made methods and to modify them if necessary.
Example:
class Command {
static name = 'ping'
static aliases = ['pong']
static botPermission = ['SEND_EMBED_LINKS']
run(message, args) {
// do something
}
}
It would be annoying adding everytime the botPermission
property to the class Command, when the Permission is used
from every Command, then it is unnecessary to add it, when you can extend the class.
It would be annoying to add the botPermission
property to the command class every time the same permissions are used
by each command. Extending the class makes this extra step obsolete.
class BaseCommand {
constructor(client) {
this.client = client
this.basePermission = ['SEND_EMBED_LINKS']
}
}
class Command extends BaseCommand {
static name = 'ping'
static aliases = ['pong']
constructor(data) {
super(data)
}
run(message, args) {
// do something
}
}
Plugins
The plugins folder helps you categorize your code into many parts to give some structure.
Of course, this has many advantages, you have a much clearer code, you can debug problems much easier.
This also opens possibilities for open source contributions, since not all parts of the code have to be published in order to add new plugins, since they are "independent".
There will be the main Plugins
folder, which by default contains a General
folder for all your base commands. The
Plugins
folder will also contain all your other plugins.
Error Handling
One of the most important things is how to handle errors. This is done to provide a user-friendly experience and to find errors faster.
You should catch errors and log them in your logger so you can fix them later. There are several open source Sentry
's
that give you a good overview of the latest errors through a website.
Sometimes errors have a positive effect on maintainability and scalability.
In addition, handling errors caused by users is very important to increase transparency. If they don't know why the error happened, then they'll be very surprised with what they did wrong and might even remove your bot from their server.
Caching
Normally libraries cache all the info they get, which can of course be helpful at the beginning to discover all functionalities but later it turns out to be a resource-consuming method. Therefore, this way should be avoided.
Discordeno allows Custom Caching
and even Custom Property Caching
which gives you fine-grained control over the
caching of data. Normally you only need 20% of the data received by Discord, which makes caching unnecessary in most
cases.
There are also some Filter
and Sweeper
methods which help you to empty unused cache values.
Cross Communication & Scaling
If you are running many different processes, such as a Welcomer API, communication is of central importance in order to send or receive data, with which you can then perform certain actions.
Cross communication can be easily done with sockets or a TCP client.
This brings up this Structure:
Bridge (Heart)
- Machine 1
- Cluster [0-9]
- Machine 2
- Cluster [10-18]
- Machine 3 -> Welcomer Api
- Machine 4 -> Dashboard
It's important to use something fast to have a proper "real time" communication.
Discordeno already offers many internal options for scaling bots, no matter what size.
As you scale, you will likely separate many parts of your bot and put them in separate processes, such as a
RestManager
, a Gateway Manager
etc.
This of course opens up a lot of possibilities:
- Zero downtime updates
- Global cache
- Synced rate limits
Check the Github Readme for more information
You just learned how to design a scalable bot, let's get into implementing it with the next pages.