Skip to main content

Facades

Understand the purpose and how to use the Athenna 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 the 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 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', ({ response }) => {
Log.channel('simple').success('New request received 😝')

return response.status(200).send({ message: 'ok' })
})

Throughout the Athenna documentation, many of the examples will use facades to demonstrate various features of the framework.

Helpers​

To complement facades, Athenna offers a variety of "helper classes" 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 helpers' 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 be created using the Facade class.

The Facade 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'
import { Context, Controller } from '@athenna/http'

@Controller()
export class AppController {
public async show({ response }: Context) {
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 the Log facade file implementation, 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'
import { Logger } from '#src/logger/Logger'

export const Log = Facade.createFor<Logger>('Athenna/Core/Logger')

The job of the createFor() method is to create a new proxy object that will have the same methods as the Logger class. So, when the user references any static method on the Log facade, Athenna will first resolve the Athenna/Core/Logger binding from the service container and them runs the requested method, (in the example above, the success() method) against that object.

Facades 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.

FacadeReference classBinding alias
LogLoggerAthenna/Core/Logger
RouteRouterAthenna/Core/HttpRoute
ServerServerImplAthenna/Core/HttpServer
MailMailImplAthenna/Core/Mail
ViewViewImplAthenna/Core/View
ArtisanArtisanImplAthenna/Core/Artisan
DatabaseDatabaseImplAthenna/Core/Database

Writing facades​

First, let's use the following command to create a new service provider to resolve our dependency inside the service container:

node artisan make:provider HelperProvider

Now let's register all of our helpers inside the register() method:

import { File } from '#app/helpers/File'
import { String } from '#app/helpers/String'
import { ServiceProvider } from '@athenna/ioc'

export class HelperProvider extends ServiceProvider {
public register() {
this.container.singleton('App/Helpers/File', File)
this.container.transient('App/Helpers/String', String)
}
}
warning

If you have not used make:provider command to create the provider, you will need to register it inside the .athennarc.json file, inside the providers array.

Artisan can generate a new facade via the make:facade command:

node artisan make:facade String

The facade file will be generated inside the providers/facades directory. Open this file and change the alias to your provider alias and set up the generic type:

import { Facade } from '@athenna/ioc'
import { String as StringImpl } from '#app/helpers/String'

export const String = Facade.createFor<StringImpl>('App/Helpers/String')

Now we can start using our new String facade:

import { Route } from '@athenna/http' // Route Facade
import { String } from '#providers/facades/String' // String Facade

Route.get('/welcome', ({ response }) => {
return response
.status(200)
.send({ hello: String.toPascalCase('world') })
})