# Athena Framework 0.18.0

**URL:** https://forum.crystal-lang.org/t/athena-framework-0-18-0/5408
**Category:** News
**Tags:** release
**Created:** [February 21, 2023, 1:51pm UTC](https://forum.crystal-lang.org/t/athena-framework-0-18-0/5408 "2023-02-21T13:51:21Z")
**Posts on this page:** 1
**Page:** 1

<div class="post-metadata">

### Author: ![Blacksmoke16](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/blacksmoke16/32/1241_2.png) [@Blacksmoke16](https://forum.crystal-lang.org/u/Blacksmoke16)
#### Post date: [February 21, 2023, 1:51pm UTC](https://forum.crystal-lang.org/t/athena-framework-0-18-0/5408/1 "2023-02-21T13:51:21Z")

</div>

# Athena Framework 0.18.0

This Framework [release](https://github.com/athena-framework/framework/releases/tag/v0.18.0) focused on polishing existing features, and more tightly integrating other components into the framework. The ecosystem itself also benefited due to some components getting updates of their own.

Highlights include the following, but read on for more specific details/examples!

- Revamped [website](https://athenaframework.org)

- [Skeleton](https://github.com/athena-framework/skeleton) template repository

- New and improved [Athena::EventDispatcher](https://athenaframework.org/EventDispatcher/) component

- Integration of the [Athena::Console](https://athenaframework.org/Console/) component

- Deprecate the Param Converter concept in favor of [value resolvers](https://athenaframework.org/Framework/Controller/ValueResolvers)

- Improve integration testing UX

- Add an `HTTP::Handler` compliant [type](https://athenaframework.org/Routing/RoutingHandler/) to allow augmenting a stdlib `HTTP::Server` with routing

- New Validation constraints

- Additional Spec features

* * *

## Revamped Website

Athena’s website has undergone some reorganization to both make it easier to navigate/know what specific pages are, but also better document the reasons why to choose Athena and some of the conventions used within the ecosystem:

- New Intro [landing](https://athenaframework.org) page
- Explanation of [benefits of Athena](https://athenaframework.org/why_athena) and what it can be used for
- Introduction to the [conventions](https://athenaframework.org/architecture/#conventions) used within the ecosystem
- Updated [getting started](https://athenaframework.org/getting_started) docs (more examples + merged in adv usages)

### Skeleton

In addition to the new website, a [skeleton](https://github.com/athena-framework/skeleton) repository template was created with the goal of making it easier to get started with the framework. It includes the suggested organizational structure, CI, and best practice READMEs throughout. It’s fairly bare bones, just so there isn’t so much to delete when first getting started with it. However there are plans for another example repository that will demonstrate what a simple blog application would look like when using the Athena Framework.

## EventDispatcher

The Athena Framework is an [events based](https://athenaframework.org/architecture/#powered-by-events) framework, meaning it emits various events during the life-cycle of a request. These events are listened upon to handle each request. All of this is orchestrated via the [Athena::EventDispatcher](https://athenaframework.org/EventDispatcher/) component, which got a major update that not only makes it simpler to use, but also more powerful from a feature/QoL perspective.

Previously, event listeners were created by including a module and defining a class method describing the events and their priority. Multiple `#call` methods could then be overloaded to handle more than one event.

> **Before**
>
> ```crystal
> @[ADI::Register]
> class CustomListener
> include AED::EventListenerInterface
> 
> def self.subscribed_events : AED::SubscribedEvents
> AED::SubscribedEvents{ATH::Events::Response => 25}
> end
> 
> def call(event : ATH::Events::Response, dispatcher : AED::EventDispatcherInterface) : Nil
> event.response.headers["FOO"] = "BAR"
> end
> end
> 
> ```

Following along with the rest of the framework, event listeners are now registered via annotating a method versus using the class method. This has a few benefits:

1. The method name is no longer limited to `call`
2. Shorter, more concise code since you no longer have to duplicate the event type twice (one in the class method and one for the actual listener method) since the event for a particular method is inferred based on the parameter’s type restriction
3. The second `dispatcher : AED::EventDispatcherInterface` parameter is now totally optional, so it can be omitted if you do not need it
4. Since annotations are known at compile time, service based listeners used in conjunction with the [Athena::DependencyInjection](https://athenaframework.org/DependencyInjection/) component, as it is within the framework; only listeners on events that are emitted will actually be instantiated. This can have a positive impact on performance if a lot of custom events are defined/used, but not for every request.

> **After**
>
> ```crystal
> @[ADI::Register]
> class CustomListener
> include AED::EventListenerInterface
> 
> @[AEDA::AsEventListener]
> def on_response(event : ATH::Events::Response) : Nil
> event.response.headers["FOO"] = "BAR"
> end
> end
> 
> ```

## Framework

This version of the Athena Framework primarily focused better integrating each component into the framework and consolidating features within the framework component itself.

### Console Integration

One of the more exciting things to be included in this release is the integration of the [console](https://athenaframework.org/architecture/console) component. Previously the Athena Framework could only be used in an HTTP context. This integration allows for an alternate CLI based entrypoint into the framework, either in addition to the HTTP side, or on its own. This most commonly can be used for internal administrative, or scheduled tasks via `cron` or something similar.

What sets the [Athena::Console](https://athenaframework.org/Console/) component apart is that offers more than just argument parsing features. It is a fully fledged framework for creating CLI applications. However it is not intended to be used to create TUIs. The component includes the ability to ask questions, create reusable styles, render tables, and provides testing abstractions to ensure your commands are doing what they should be.

Similar to the newly refactored `Athena::EventDispatcher` component, console commands are registered via an annotation, and when also registered as services via the [Athena::DependencyInjection](https://athenaframework.org/DependencyInjection/) component, they will be automatically registered with the additional benefit of only instantiating the command class(es) that are being executed. As an added bonus of using the DI component, services may be shared between controllers and commands. Just one of the benefits of following the [SOLID Principles](https://athenaframework.org/why_athena/#solid-principles).

> **Example Usage**
>
> ```crystal
> @[ACONA::AsCommand("app:create-user")]
> @[ADI::Register]
> class CreateUserCommand < ACON::Command
> protected def configure : Nil
> # ...
> end
> 
> protected def execute(input : ACON::Input::Interface, output : ACON::Output::Interface) : ACON::Command::Status
> # Implement all the business logic here.
> 
> # Indicates the command executed successfully.
> Status::SUCCESS
> end
> end
> 
> ```

#### Built-In Commands

The framework component takes this a step further by baking in some commonly useful commands to aid with debugging your application. This includes:

- [`ATH::Commands::DebugEventDispatcher`](https://athenaframework.org/Framework/Commands/DebugEventDispatcher) - Display configured listeners for an application
- [`ATH::Commands::DebugRouter`](https://athenaframework.org/Framework/Commands/DebugRouter) - Display current routes for an application
- [`ATH::Commands::DebugRouterMatch`](https://athenaframework.org/Framework/Commands/DebugRouterMatch) - Simulate a path match to see which route, if any, would handle it

> **Debug Event Dispatcher**
>
> ```sh
> $ ./bin/console debug:event-dispatcher
> Registered Listeners Grouped by Event
> =====================================
> 
> Athena::Framework::Events::Action event
> ---------------------------------------
> 
> ------- -------------------------------------------------------- ----------
> Order Callable Priority
> ------- -------------------------------------------------------- ----------
> #1 Athena::Framework::Listeners::ParamFetcher#on_action 5
> ------- -------------------------------------------------------- ----------
> 
> Athena::Framework::Events::Exception event
> ------------------------------------------
> 
> ------- -------------------------------------------------- ----------
> Order Callable Priority
> ------- -------------------------------------------------- ----------
> #1 Athena::Framework::Listeners::Error#on_exception -50
> ------- -------------------------------------------------- ----------
> 
> Athena::Framework::Events::Request event
> ----------------------------------------
> 
> ------- -------------------------------------------------- ----------
> Order Callable Priority
> ------- -------------------------------------------------- ----------
> #1 Athena::Framework::Listeners::CORS#on_request 250
> #2 Athena::Framework::Listeners::Format#on_request 34
> #3 Athena::Framework::Listeners::Routing#on_request 32
> ------- -------------------------------------------------- ----------
> 
> ...
> 
> ```

> **Debug Router**
>
> ```sh
> $ ./bin/console debug:router
> ---------------- ------- ------- ----- --------------------------------------------
> Name Method Scheme Host Path
> ---------------- ------- ------- ----- --------------------------------------------
> homepage ANY ANY ANY /
> contact GET ANY ANY /contact
> contact_process POST ANY ANY /contact
> article_show ANY ANY ANY /articles/{_locale}/{year}/{title}.{_format}
> blog ANY ANY ANY /blog/{page}
> blog_show ANY ANY ANY /blog/{slug}
> ---------------- ------- ------- ----- --------------------------------------------
> 
> ```

### Value Resolvers

As mentioned in the [Athena 0.17.0](https://forum.crystal-lang.org/t/athena-0-17-0/4624#future-of-param-converters-5) release announcement, value resolves were intended to replace the previous `ParamConverter` implementation. This release does just that, fully replacing them with [value resolvers](https://athenaframework.org/Framework/Controller/ValueResolvers). Value resolvers are more powerful in a couple ways:

1. The [order](https://athenaframework.org/Framework/Controller/ValueResolvers/) in which value resolvers are applied can be controlled unlike param converters. This allows for creating a hierarchy that tries more specialized resolvers before less specific ones, which can also be to override/customize built-in resolvers if so desired.
2. They provide full access to the underlying [parameter](https://athenaframework.org/Framework/Controller/ParameterMetadata/), which can be used to automatically know if a given resolver should be invoked based on the name or type of the parameter for example.
3. Due to the previous point, there is no need to add a dedicated `@[ATHA::ParamConverter]` annotation just because you want to consume a datetime string into a `Time` instance. The resolver is applied automatically if possible just by typing that parameter as `Time`.
4. Thanks to [https://github.com/crystal-lang/crystal/pull/12044](https://github.com/crystal-lang/crystal/pull/12044), annotations may now be applied directly to specific parameters. This provides a much more robust way of handling [resolver configuration](https://athenaframework.org/Framework/Controller/ValueResolvers/Interface/#Athena::Framework::Controller::ValueResolvers::Interface--extra-data) that doesn’t involve mapping the information from an annotation on the method solely based on the name of the parameter.
5. While some resolvers can be globally enabled based on the type/name of the parameter, that is not true for all. In cases where it is required to be more explicit, the presence of an annotation may be used to enable a particular resolver. Such as knowing if it’s value should be extracted from the [request body](https://athenaframework.org/Framework/Controller/ValueResolvers/RequestBody/).
6. More flexibility and type safety. If using an annotation to enable the resolver, you can have it define [multiple overloads](https://athenaframework.org/Framework/Controller/ValueResolvers/Interface/#Athena::Framework::Controller::ValueResolvers::Interface--handling-multiple-types) to handle parameters of different types as well as [limit what types a resolver supports](https://athenaframework.org/Framework/Controller/ValueResolvers/Interface/#Athena::Framework::Controller::ValueResolvers::Interface--strict-typing) at compile time.

### Spec Improvements

Integration testing controller actions is a very beneficial way of validating their correctness. They not only test the logic within the action itself, but also its integration with the rest of the framework, such as value resolvers, event listeners, etc. However, when writing these tests, it may not be that obvious _why_ a particular test is not passing as expected. This release introduces some DX changes to help alleviate that.

#### Expectation Methods

The newly introduced [ATH::Spec::Expectations::HTTP](https://athenaframework.org/Framework/Spec/Expectations/HTTP/) module adds numerous helper expectation methods, such as asserting the response is successful, has a specific header/cookie (value), and/or if the request has an attribute with a specific value. All the while providing actually useful debugging output if that is not the case. For example if you were to use `self.assert_response_is_successful`, and the request was _NOT_ successful, it’ll fail telling you that, but also will include the response headers and body as clues to help figure out why that was the case. Additionally if an exception occurred, its message/location will be included in the output.

> **Example Output**
>
> ```sh
> Failed asserting that the response is successful:
> HTTP/1.1 500 Internal Server Error
> 
> content-type: application/json; charset=UTF-8
> x-debug-exception-message: Oh%20noes
> x-debug-exception-class: Exception
> x-debug-exception-code: 500
> x-debug-exception-file: src/controllers/example_controller.cr:4:5
> cache-control: no-cache, private
> date: Mon, 20 Feb 2023 23:15:27 GMT
> Content-Length: 46
> 
> {"code":500,"message":"Internal Server Error"}
> 
> Caused By:
> Oh noes (Exception)
> from src/controllers/example_controller.cr:4:5
> 
> ```

## Routing

While not directly related to the new Framework version, the underlying routing component [Athena::Routing](https://athenaframework.org/Routing/) got an exciting new feature of its own.

### Routing Handler

The stdlib’s [HTTP::Server](https://crystal-lang.org/api/HTTP/Server.html) provides a pretty solid HTTP server implementation, but it lacks one critical feature for anything more than the most basic use cases: routing. The routing component recently introduced the [ART:::RoutingHandler](https://athenaframework.org/Routing/RoutingHandler/) type that can provide this capability in a low-overhead manner. It provides no additional features beyond routing, and as such can be a good option for simple use cases as opposed to being required to use a framework, just for its routing features.

> **Example Usage**
>
> ```crystal
> handler = ART::RoutingHandler.new
> 
> # The `methods` property can be used to limit the route to a particular HTTP method.
> handler.add "new_article", ART::Route.new("/article", methods: "post") do |ctx|
> pp ctx.request.body.try &.gets_to_end
> end
> 
> # The match parameters from the route are passed to the callback as a `Hash(String, String?)`.
> handler.add "article", ART::Route.new("/article/{id<\\d+>}", methods: "get") do |ctx, params|
> pp params # => {"_route" => "article", "id" => "10"}
> end
> 
> # Call the `#compile` method when providing the handler to the handler array.
> server = HTTP::Server.new([
> handler.compile,
> ])
> 
> address = server.bind_tcp 8080
> puts "Listening on http://#{address}"
> server.listen
> 
> ```

## Validator

Similar to the routing component, the [Athena::Validator](https://athenaframework.org/Validator/) component got its own feature that may prove useful outside of the framework.

### Collection Constraint

Previously the only way to validate a collection of data was to first create a type, include `AVD::Validatable`, define instance variables, apply the validation annotations to them, and then ultimately have some way to create an instance of this type based on the underlying data. This is fairly straightforward for JSON data via `JSON::Serializable`, or the serializer component, but less so for other forms of data, especially if it is already in a deserialized form.

The [AVD::Constraints::Collection](https://athenaframework.org/Validator/Constraints/Collection/) can be used for just this purpose. It accepts a `Hash` where the keys represent the keys in the collection, and the value is the constraint(s) used to validate the value at that key. Any `Enumerable({K,V})`, including `URI::Params` or any custom type. The constraint also supports presence and absence of fields, both on a global and per key basis. See the constraint API docs for more information.

> **Example Usage**
>
> ```crystal
> constraint = AVD::Constraints::Collection.new({
> "email" => AVD::Constraints::Email.new,
> "email_signature" => [
> AVD::Constraints::NotBlank.new,
> AVD::Constraints::Size.new(..100, max_message: "Your signature is too long"),
> ],
> "alternate_email" => AVD::Constraints::Optional.new([
> AVD::Constraints::Email.new,
> ] of AVD::Constraint),
> })
> 
> data = {
> "email" => "...",
> "email_signature" => "...",
> }
> 
> validator.validate data, constraint
> 
> ```

## Spec

The [Athena::Spec](https://athenaframework.org/Spec/) module is the unsung hero of the Athena ecosystem. It’s alternate [Spec](https://crystal-lang.org/api/Spec.html) DSL powers the vast majority of the testing types provided by each component. But since it is _NOT_ its own test runner, the stdlib’s `Spec` module may also be used on its own depending on the context. New versions of the spec module have been released that added some helpful new features to make testing your application even easier.

### `ASPEC::TestCase::TestWith`

Instead of created a dedicated methods for use with [ASPEC::TestCase::DataProvider](https://athenaframework.org/Spec/TestCase/DataProvider/#Athena::Spec::TestCase::DataProvider), you can define a data set using the [ASPEC::TestCase::TestWith](https://athenaframework.org/Spec/TestCase/TestWith/#Athena::Spec::TestCase::TestWith) annotation. The annotations accepts a variadic amount of `Tuple` positional/named arguments and will create a `it` case for each “set” of data.

> **Example Usage**
>
> ```crystal
> struct TestWithTest < ASPEC::TestCase
> @[TestWith(
> two: {2, 4},
> three: {3, 9},
> four: {4, 16},
> five: {5, 25},
> )]
> def test_squares(value : Int32, expected : Int32) : Nil
> (value ** 2).should eq expected
> end
> 
> @[TestWith(
> {2, 8},
> {3, 27},
> {4, 64},
> {5, 125},
> )]
> def test_cubes(value : Int32, expected : Int32) : Nil
> (value ** 3).should eq expected
> end
> end
> 
> TestWithTest.run # =>
> # TestWithTest
> # squares two
> # squares three
> # squares four
> # squares five
> # cubes 0
> # cubes 1
> # cubes 2
> # cubes 3
> 
> ```

### Codegen Support

The spec component’s `assert_error` and `assert_success` methods can be a useful tool in testing custom compile time errors. However in some cases a runtime assertion needs to be tested in isolation. These methods now support a `codegen: true` named argument that can be used to handle this. Without it, the code is only validated to compile, excluding any runtime exceptions.
