Athena 0.9.0
Tags: crystal, ruby, symfony, framework
Published: 2020-06-13 01:18:42

---
title: Athena 0.9.0
published: true
description: Release notes for Athena 0.9.0
tags: crystal, ruby, symfony, framework
---

# Athena 0.9.0

With [Crystal 0.35.0](https://crystal-lang.org/2020/06/09/crystal-0.35.0-released.html) finally released, I'm happy to also announce the release of [Athena 0.9.0](https://github.com/athena-framework/athena/releases/tag/v0.9.0)!

This release focused on further refining the overall foundation of the framework.  This includes:
* A new [Getting Started](https://athena-framework.github.io/athena/Athena/Routing.html#getting-started) section
* An overhaul of the DI framework
* A refactor of how controller action arguments are resolved
* A refactor of how param converters work
* Compile time route collision detection
* Some additional minor QoL improvements

See the linked types for more details/examples.

## DI Overhaul

Athena `0.9.0` comes bundled with [Athena Dependency Injection 0.2.0](https://github.com/athena-framework/dependency-injection/releases/tag/v0.2.0), which overhauls the implementation of the service container; making the usage of it simpler, more feature rich, easier to maintain, and more robust.

In version `0.1.x`, services were required to include the `ADI::Service` module, as well as specify any service dependencies within the [ADI::Register](https://athena-framework.github.io/dependency-injection/Athena/DependencyInjection/Register.html) annotation as position arguments.  This is no longer required as dependencies are now resolved automatically based on type restrictions.  The module is also no longer required.

> **NOTE:** I'm using records for brevity, a `struct` service behaves differently than a `class` service.  Be sure to pick the right one for your use case.  See [the docs](https://athena-framework.github.io/dependency-injection/Athena/DependencyInjection/Register.html#overview) for more details.

```crystal
# Before
@[ADI::Register]
record ServiceOne do
  include ADI::Service
end

@[ADI::Register("@my_service", true)]
record ServiceTwo, service : ServiceOne, debug : Bool do
  include ADI::Service
end

# After
@[ADI::Register]
record ServiceOne

@[ADI::Register(_debug: true)]
record ServiceTwo, service : ServiceOne, debug : Bool
```

By default, only services can be auto resolved, however [ADI.bind](https://athena-framework.github.io/dependency-injection/Athena/DependencyInjection.html#bind(key,value)-macro) can be use to allow auto resolving [Scalar Arguments](https://athena-framework.github.io/dependency-injection/Athena/DependencyInjection/Register.html#scalar-arguments) and [Tagged Services](https://athena-framework.github.io/dependency-injection/Athena/DependencyInjection/Register.html#tagging-services).  [Service Aliases](https://athena-framework.github.io/dependency-injection/Athena/DependencyInjection/Register.html#aliasing-services) can be used to define a "default" service for a given interface.  Service dependencies can also be declared as [optional](https://athena-framework.github.io/dependency-injection/Athena/DependencyInjection/Register.html#optional-services) or even based on [Generic Services](https://athena-framework.github.io/dependency-injection/Athena/DependencyInjection/Register.html#generic-services).

## Action Handling Refactor

The logic that resolves a specific action argument has been decoupled from the logic that resolves the arguments for an action.  This is mainly a behind the scenes change, but does come with some user facing changes.  The main one being the request's attributes.  Prior versions of Athena exposed a `Hash` on the `HTTP::Request` object that could be used to store arbitrary data specific to the request's life-cycle.  This has been replaced with [ART::ParameterBag](https://athena-framework.github.io/athena/Athena/Routing/ParameterBag.html); the concept is the same, but the API is better and is more robust.

The `ART::ParameterBag` is also where the path/query parameters are stored.  In fact, any value stored within it is able to be automatically provided to the controller action.  This is accomplished via [ART::Arguments::Resolvers::RequestAttribute](https://athena-framework.github.io/athena/Athena/Routing/Arguments/Resolvers/RequestAttribute.html) which looks for a value in the bag with the same name as the controller argument.  Custom resolves can also be defined by creating an implementation of [ART::Arguments::Resolvers::ArgumentValueResolverInterface](https://athena-framework.github.io/athena/Athena/Routing/Arguments/Resolvers/ArgumentValueResolverInterface.html).

## Param Converter Refactor

[ART::ParamConverterInterface](https://athena-framework.github.io/athena/Athena/Routing/ParamConverterInterface.html)s have undergone a rewrite.  The API has changed to no longer return the converted value.  It should instead, most commonly, be stored in the request's attributes.  Param converters are now also services themselves; this allows using DI to supply any require dependencies needed for the conversion process.  The concept of [ART::ParamConverterInterface::ConfigurationInterface](https://athena-framework.github.io/athena/Athena/Routing/ParamConverterInterface/ConfigurationInterface.html) has also been introduced to allow defining extra configuration data that should be read from the [ART::ParamConverter](https://athena-framework.github.io/athena/Athena/Routing/ParamConverter.html) annotation.

```crystal
require "athena"

@[ADI::Register]
struct MultiplyConverter < ART::ParamConverterInterface
  configuration by : Int32

  # :inherit:
  def apply(request : HTTP::Request, configuration : Configuration) : Nil
    arg_name = configuration.name
    return unless request.attributes.has? arg_name
    value = request.attributes.get arg_name, Int32
    request.attributes.set arg_name, value * configuration.by, Int32
  end
end

class ParamConverterController < ART::Controller
  @[ART::Get(path: "/multiply/:num")]
  @[ART::ParamConverter("num", converter: MultiplyConverter, by: 4)]
  def multiply(num : Int32) : Int32
    num
  end
end

ART.run

# GET /multiply/3 # => 12
```

A built in [ART::TimeConverter](https://athena-framework.github.io/athena/Athena/Routing/TimeConverter.html) that converts a date(time) string into a `Time` instance has also been added.

## Route Collision Detection

Athena will now raise a compile time error if two routes share the same path; either statically, or with path arguments in the same locations.

```crystal
class TestController < ART::Controller
  @[ART::Get(path: "some/path/:id")]
  def action1(id : Int64) : Int64
    id
  end
end

class OtherController < ART::Controller
  @[ART::Get(path: "some/path/:id")]
  def action2(id : Int64) : Int64
    id
  end
end

ART.run

# #=> Route action OtherController#action2's path "/some/path/:id" conflicts with TestController#action1's path "/some/path/:id".
```

## Other Minor Improvements

* Introduced the concept of [ART::Response::Writer](https://athena-framework.github.io/athena/Athena/Routing/Response/Writer.html) to control *how* the response content is written to the response IO
* Allow [ART::Response](https://athena-framework.github.io/athena/Athena/Routing/Response.html#new(status:HTTP::Status|Int32=HTTP::Status::OK,headers:HTTP::Headers=HTTP::Headers.new,&block:IO->Nil)-class-method) to write directly to the response IO
* Add some additional [ART::Exceptions](https://athena-framework.github.io/athena/Athena/Routing/Exceptions.html) types, and make defining custom types easier

As usual feel free to join me in the Athena [Gitter](https://gitter.im/athena-frameworkcr/community) channel if you have any suggestions, questions, or ideas. I'm also available on Discord (`Blacksmoke16#0016`) or via [Email](mailto:george@dietrich.app).