Application
Application
acts as a container object for all the domain objects, features
and layers of the software you develop.
Baking an Application
Baked provides a fluent interface to create an application. You can access it
through Bake.New
under Baked
namespace. Below is a sample Program.cs
that
creates and runs a new application;
Bake.New
.Application(app =>
{
...
})
.Run();
Baked
is automatically added as a global using so that you can directly make a call toBake.New
.
Bake
always sets current culture toCultureInfo.InvariantCulture
to avoid case conversion errors.
Adding Extensions
To add a new extension to an application, you need to make use of the feature / layer system.
Baked does not allow you to use build.Services.Add
or app.Use
methods
directly. This is a design decision to avoid any unexpected behavior because the
order of extensions may require a special attention as in Enabling Cors.
As
method provides an ApplicationDescriptor
instance that has Layers
and
Features
properties. Using these properties, you can add desired extensions
into your application.
Layers and features come with extension methods exposed directly in
Baked
namespace so that you can see options without adding an extrausing
.
Layers
Layers are added without any options to configure;
app.Layers.AddDomain();
app.Layers.AddHttpServer();
app.Layers.AddDataAccess();
A layer can only be added once.
To configure a layer, you need to add a feature mentioned in the next section.
Features
Features are added using one of the implementations available in given configurator;
app.Features.AddGreeting(c => c.WelcomePage());
app.Features.AddDatabase(c => c.MySql());
A feature can only be added once.
An implementation may ask for additional options within its configurator method;
app.Features.AddDatabase(c => c.MySql(autoUpdateSchema: true));
Overriding A Configuration
To override configurations of added features, follow
feature conventions, create a
ConfigurationOverrider
feature and add your overrider after all other
features as shown below;
app.Features.AddGreeting(c => c.WelcomePage());
app.Features.AddDatabase(c => c.MySql());
...
app.Features.AddConfigurationOverrider();
Running an Application
To run an application you need to call Run()
method after baking it.
Bake.New
.Application(app =>
{
...
})
.Run();
Application runs in phases provided by its layers. For example
HttpServerLayer
uses ASP.NET Core to build a web application which typically
runs in three phases;
These phases come from layers using GetPhases()
method of ILayer
. In the
above example, HttpServerLayer
(ASP.NET Core) introduced these three phases.
At the beginning of each phase, application initializes it by providing an
ApplicationContext
instance. This way each phase can add/get certain objects
to/from the context, such as IServiceCollection
, IMiddlewareCollection
,
IEndpointRouteBuilder
etc.
Refer to Readiness via Dependencies
for more details.
When trying to get a certain object from
ApplicationContext
, exact type should be given. Using any other type that extends or implements the target object will result in an unsuccessfulGet
operation. For example, trying toGet
aWebApplication
usingIApplicationBuilder
type will be unsuccessful.
As mentioned earlier, layers provide features with things
to configure. For this to happen, application asks every layer what to configure
at each phase. If a layer has something to get configured at a phase, such as
the IMiddlewareCollection
at the Build phase, it returns that object within
a phase context in ILayer.GetContext()
. Using this phase context, application
passes the provided object(s) to all of the features through
IFeature.Configure()
. Phase contexts are disposed at the end of their phase.
Below sequence diagram shows how an application runs in phases. In this diagram
there are two layers (A
and B
), each having one phase, and two features
(X
and Y
);
A layer doesn't necessarily introduce new phases to an application, but all phases are applied to all layers nevertheless. For example,
HttpServerLayer
introduces Build phase which is applied toDomain
,HttpServer
andDataAccess
layers to allow them provide their configuration specific to the Build phase.
Order of Phases
Initialization order of phases are determined by;
- Readiness of a phase through
IPhase.IsReady
property, - Value of
IPhase.Order
property, - And the order they are added to the application.
Application checks if a phase is ready and if it is not, application keeps it for the next iteration of initializations.
A phase makes use of given application context to determine if it is ready to
be initialized or not. For example, Run
phase looks for WebApplication
in
the given context. If it does not, phase decides not to initialize. This way
application knows that Run
phase is not ready until some other phase, e.g.,
Build
phase, adds WebApplication
to the context.
All phases need to initialize. Application will cause a
CannotProceedException
if any of the phases never gets initialized.
When two phases are ready to be initialized at the same iteration, application
sorts them by their Order
property. A phase can have the following orders;
- Earliest
- Early
- Normal
- Late
- Latest
Application may have any number of phases with the same order at the same iteration. So in that case, they will initialize in the order they are provided from layers.
Earliest and Latest Phases
There should be only one earliest phase and one latest phase per iteration.
If there are multiple earliest or latest phases, application will throw an
OverlappingPhaseException
and execution will stop. If you see this exception,
it means you need to reorder phases so that they don't overlap, or you might
change their readiness so that they run in their own iteration.
These special values ensures certain phases are initialized firstly or lastly.
For example, the first phase of HttpServerLayer
, Create Builder, adds
WebApplicationBuilder
to the application context, which should be the first
thing to be done when running an application. To ensure this, Create Builder
should be able to declare itself to be the only earliest phase of its
iteration.