Application Lifecycle
Understand each one of the Athenna applications lifecycle.
Introduction
When using any tool in the "real world", you feel more confident if you understand how that tool works. Application development is no different. When you understand how your development tools function, you feel more comfortable and confident using them.
The goal of this document is to give you a good, high-level overview of how the Athenna framework works. By getting to know the overall framework better, everything feels less "magical" and you will be more confident building your applications. If you don't understand all the terms right away, don't lose heart! Just try to get a basic grasp of what is going on, and your knowledge will grow as you explore other sections of the documentation.
Athenna foundation lifecycle
The Athenna foundation is everything that is not coupled to the type of application you are using. Meaning that no matter what is the type of application you are using to build your solution, the explanation bellow is valid for all of them.
The entry point of an Athenna application is the
Path.bin('main.ts')./bin/main.ts
Loading the foundation
Rc file
The RC file is the first thing Athenna will do when booting the application. The RC file is responsible to configure your entire workspace and certain runtime settings of your application.
Other operations
Some other operation will be done when loading the foundation, such as:
- Verify if your engines in
package.jsonis supported. - Register the Athenna logger provider.
- Register an uncaught exception handler.
- Define your application root path.
- Register
Igniteclass in the service container. - Define application signals for graceful shutdown.
The process of loading the Athenna foundation is triggered by the
Ignite.load() method.
Firing the foundation
The Athenna foundation will automatically be fired internally depending on the type of application you are using, let's see all the operations executed in order to get Athenna foundation done:
Environment variables
The first thing Athenna will do when firing the foundation is to load your environment variables files. You can learn more about what is and how to configure your environment variables in the environment configuration documentation section.
Configuration files
Afterwards Athenna will load all the configuration files found inside the path returned by the
Path.config()./src/config
Service providers
One of the most important kernel bootstrapping actions is loading the
service providers for your application. All the service providers for
the application are configured in the .athennarc.json in the
providers array.
Athenna will iterate through this list of providers and instantiate each
of them. After instantiating the providers, the register method will
be called on all the providers. Then, once all the providers have been
registered, the boot method will be called on each provider. This is
so service providers may depend on every container binding being
registered and available by the time their boot method is executed.
Service providers are responsible for bootstrapping all the frameworks various components, such as the database, http server, validation, services and routing components. Essentially, every major feature offered by Athenna is bootstrapped and configured by a service provider. Since they bootstrap and configure so many features offered by the framework, service providers are the most important aspect of the entire Athenna bootstrap process.
Preloads
Preload files are loaded at the time of booting the application. The
files are loaded right after booting the service providers. A preload
file could be useful to execute some operation before bootstrapping
the application. All the preloads for the application are configured
in the .athennarc.json in the preloads array.
The process of firing the Athenna foundation is triggered by the
Ignite::fire() static method. But if you check your
Path.bin('main.ts')./bin/main.ts
-
The
REST APIapplication needs to fire the foundation first because it depends on service providers to register your controllers, services, middlewares, routes, etc. -
The
CLIapplication and Artisan commands does not fire the foundation because commands do not depend on registering providers, loading environment variables and configuration files, for example. But, if your command got theloadAppsetting equals totruein.athennarc.json, the foundation will be fired before executing your command.
REST API lifecycle
Kernel
The Kernel class is responsible by defining some bootstraps that will be run before reading your
Path.routes('http.ts')./src/routes/http.ts
The Kernel is also responsible by registering your middlewares and
controllers defined in your .athennarc.json file. By default, Athenna
will always use the default implementation HttpKernel class imported
from @athenna/http package. If you prefer, you can create your custom
Kernel implementation, extending the default HttpKernel class and
registering it in your Ignite.httpServer method call:
import { HttpKernel } from '@athenna/http'
export class CustomKernel extends HttpKernel {
}
You can check all the methods available for you to override in your custom kernel implementation taking a look at HttpKernel implementation code.
Then, you can register your CustomKernel in your
Path.bin('main.ts')./bin/main.ts
import { Ignite } from '@athenna/core'
const ignite = await new Ignite().load(import.meta.url)
await ignite.httpServer({ kernelPath: '#src/http/CustomKernel' })
Routes
The
Path.routes('http.ts')./src/routes/http.ts
One of the most important service providers in your application is the
HttpRouteProvider. This service provider adds in the container the
Route class instance used inside
Path.routes('http.ts')./src/routes/http.ts
When the client request arrives, the server first executes all your global middlewares, then it will execute all your route middlewares. Once it finishes, it goes for your handler/controller. See the example:
Finish up
Once the controller/handler function returns a response, the response will travel back outward through each global interceptor, and then route's interceptor, giving the application a chance to modify or examine the outgoing response. See the example:
As you can see in the example, the response content is sent to the client. The request finishes for the client but not for the server. Now it's time to execute the global and route terminators. The terminators are executed when a response has been sent, so you will not be able to send more data to the client. It can, however, be useful for sending data to external services, for example, create metrics of the entire request. See the example:
Finally, once all terminators are executed, the request finishes in the server. We've finished our journey through the entire REST API lifecycle 🥳.
Cli and Commands lifecycle
Kernel
The Kernel class is responsible for defining some bootstraps that will be run before registering your commands. These bootstraps configure error handling for commands, detect the application environment, and perform other tasks that need to be done before the command is actually handled. Typically, these classes handle internal Athenna configuration that you do not need to worry about.
The Kernel is also responsible for registering your commands defined in your
.athennarc.json file. By default, Athenna will always use the default
implementation ConsoleKernel class imported from @athenna/http package. If
you prefer, you can create your custom Kernel implementation, extending the
default ConsoleKernel class and registering it in your Ignite.console() method
call:
import { ConsoleKernel } from '@athenna/http'
export class CustomKernel extends ConsoleKernel {
}
You can check all the methods available for you to override in your custom kernel implementation taking a look at ConsoleKernel implementation code.
Then, you can register your CustomKernel in your
Path.bin('main.ts')./bin/main.ts
Path.bin('artisan.ts')./bin/artisan.ts
import { Ignite } from '@athenna/core'
const ignite = await new Ignite().load(import.meta.url, {
bootLogs: false
})
await ignite.console(process.argv, {
kernelPath: '#src/http/CustomKernel'
})
Execution
The
Path.routes('console.ts')./src/routes/console.ts
commands property of
.athennarc.json file is where that we define all ours commands
and the handlers who will handle the terminal arguments.
When the terminal arguments arrive, the application will be bootstrapped
based on the command that you are asking to execute. Let's suppose we have
executed the hello command defined in our .athennarc.json file:
{
"commands": {
"hello": {
"path": "#src/console/commands/HelloCommand",
"env": "local",
"loadApp": false,
"stayAlive": false,
"loadAllCommands": false,
"environments": ["console"]
}
}
}
Since loadAllCommands is set to false, the Kernel will load only the hello
command and execute it:
Finish up
Once the command handler function finish, Athenna will verify if the stayAlive
setting is set to true, if so, the application will not be terminated, very
useful when running command like repl and serve.
We've finished our journey through the entire command lifecycle 🥳.
Focus on service providers
Service providers are truly the key to bootstrapping an Athenna application. The application instance is created, the service providers are registered, and the request is handed to the bootstrapped application. It's really that simple!
Having a firm grasp of how an Athenna application is built and bootstrapped via
service providers is very valuable. Your application's default service providers
are stored in the providers directory, and you can create your own provider
with the following command:
node artisan make:provider AppProvider
With this new provider you can add your application's own bootstrapping and service container bindings. For large applications, you may wish to create several service providers, each with more granular bootstrapping for specific services used by your application.