Facades
Introduction
Throughout the Athenna documentation, you will see examples of code that interacts with Athenna features via "facades".
Facades provide a "static" interface to class that are available in the application's service container
.
Athenna ships with many facades which provide access to almost all of Athenna features.
Athenna facades serve as "static proxies" to underlying classes in the service container, providing the benefit of a terse, expressive syntax while maintaining more testability and flexibility than traditional static methods. It's perfectly fine if you don't totally understand how facades work under the hood - just go with the flow and continue learning about Athenna.
All of Athenna facades are defined in their own packages. So, we can easily access a facade like so:
import { Log } from '@athenna/logger' // Log Facade
import { Route } from '@athenna/http' // Route Facade
Route.get('/welcome', function ({ response }) {
Log.channel('simple').success('New request received 😝')
return response.status(200).send({ hello: 'world' })
})
Throughout the Athenna documentation, many of the examples will use facades to demonstrate various features of the framework.
Helper functions
To complement facades, Athenna offers a variety of global "helper functions" that make it even easier to interact with
common Athenna features. Some common helper functions you may interact with are Env
, Config
, Path
, and more. Each
helper function offered by Athenna is documented with their corresponding feature; however, a complete list is available
within the dedicated helper documentation
.
When to use facades
Facades have many benefits. They provide a terse, memorable syntax that allows you to use Athenna features without remembering long class names that must be injected or configured manually.
However, some care must be taken when using facades. The primary danger of facades is class "scope creep". Since facades are so easy to use and do not require injection, it can be easy to let your classes continue to grow and use many facades in a single class. Using dependency injection, this potential is mitigated by the visual feedback a large constructor gives you that your class is growing too large. So, when using facades, pay special attention to the size of your class so that its scope of responsibility stays narrow. If your class is getting too large, consider splitting it into multiple smaller classes.
How facades work
In an Athenna application, a facade is a class that provides access to an object from the container. The machinery that
makes this work is in the Facade
class. Athenna facades, and any custom facades you create, will extend the base
Facade
class.
The Facade
base class makes use of the Proxy API
to defer calls from your facade to an object resolved from the
container. In the example below, a call is made to the Athenna log system. By glancing at this code, one might assume
that the static success
method is being called on the Log
class:
import { Log } from '@athenna/logger'
export class WelcomeController {
/**
* Show the welcome payload.
*
* @param {import('@athenna/http').ContextContract} ctx
* @return any
*/
async show({ response }) {
Log.success('Welcome to Athenna 😝')
return response.status(200).send({ hello: 'world' })
}
}
Notice that near the top of the file we are importing the Log
facade. This facade serves as a proxy for accessing the
underlying implementation of the Logger
class from Athenna service container. Any calls we make using the facade will
be passed to the underlying instance of Athenna logger class.
If we look at that Log
, you'll see that Log
is just a constant that points to the alias Athenna/Core/Logger
that is
registered inside the service container:
import { Facade } from '@athenna/ioc'
/** @type {Facade & import('#src/index').Logger} **/
export const Log = Facade.createFor('Athenna/Core/Logger')
This method's job is to return the name of a service container binding. When a user references any static method on the
Log
facade, Athenna resolves the Athenna/Core/Logger
binding from the service container
and runs the requested method (in this case, success
) against that object.
Facade class reference
Below you will find every facade and its underlying class. This is a useful tool for quickly digging into the API documentation for a given facade root. The service container binding key is also included where applicable.
Facade | Reference class | Service container binding alias |
---|---|---|
Log | Logger | Athenna/Core/Logger |
Server | Http | Athenna/Core/HttpServer |
Route | Router | Athenna/Core/HttpRoute |
Artisan | Artisan | Athenna/Core/Artisan |
Database | Database | Athenna/Core/Database |
Writing facades
First you need to create a new service provider
to resolve your dependency inside the service container
. Just run
node artisan make:provider HelperProvider
and register your binding:
import { String } from '#app/Helpers/String'
import { ServiceProvider } from '@athenna/ioc'
export class HelperProvider extends ServiceProvider {
/**
* Register any application services.
*
* @return {void}
*/
register() {}
/**
* Bootstrap any application services.
*
* @return {void}
*/
boot() {
this.container.bind('App/Helpers/String', String)
}
}
caution
If you have not used node artisan make:provider
command, you will need to register your HelperProvider
inside config/app.js
file inside the providers
array.
The Artisan can generate a new Facade via the make:facade
command:
node artisan make:facade String
The Facade file will be generated inside providers/Facades
folder. Open this file and change the alias to your
provider alias:
import { Facade } from '@athenna/ioc'
/** @type {Facade & import('#app/Helpers/String').String} */
export const String = Facade.createFor('App/Helpers/String')
Now we can start using our brand new String
facade:
import { Route } from '@athenna/http' // Route Facade
import { String } from '#providers/Facades/String' // String Facade
Route.get('/welcome', function ({ response }) {
return response.status(200).send({ hello: String.toPascalCase('world') })
})