Revisiting Apux
Today I revisit a Flux inspired pattern for ASP.NET Core, how our internal use led to it’s refactor, and how to upgrade your projects to deal with the new changes.
I’ve recently started a new job and part of the learning process often involves new patterns, libraries and languages. An example of such a library is the Apux pattern created by my colleague, bearded man and all around great guy Dan Harris. You can read the original post here. While I love the feel of the library I thought I would take another pass and see if we could tweak the overall structure. As a new employee it not only helped me make my mark, and show what I could bring to the team, but it really helped me understand the fundamentals of a workflow I had not used before.
You can find Dan’s original example here along with my pull request for the refactor here.
The Problem
One of the core principles of Apux is that it has a dynamic payload. The problem with dynamic payloads and strictly typed languages is that things can get messy fast. For most of its life a request sent to an API using Apux doesn’t even care about the payload, but rather the type, to inform where it goes next. A solution might be to implement generic constraints but before you know it you’re having to implement these constraints all over your program and things become a little unmanageable. Much like the viral nature of async / await
, adding generic constraints in this instance would require you to implement them everywhere.
The second problem was that when a payload finally needed to be used it was ambiguous to the programmer which of the two implemented payload variables to use in the original implementation.
The Refactor
I will skip over most of the refactoring in this section purely because the library itself was already pretty good and most of my changes were to code style and do not effect how Apux works. However, I would like to show why things were refactored and how they have changed the way we use Apux.
Originally an ApuxAction
implemented the following interface:
Knowing that our payload is dynamic is tricky, it could literally be anything! So saving that information as a parsed JToken
was a nice way to go, but notice we have a bit of a problem when we look at a snippet of the ApuxAction
itself.
In order for us to use our value directly (and not cast it every time we needed it) another payload needed to be exposed. Due to our interface we cannot make BasePayload
protected either leading to confusion about which payload to use. Additionally, we found many instances in code where an ApuxAction
was being created with an unnecessary generic type of JToken
as a way to circumvent our generic typing but still keep our payload dynamic. It was clear we needed to encapsulate the ApuxAction
and make generic constraints less important so it was time to refactor. The former problems were solved by removing interfaces from ApuxAction
and ApuxActionResult
and instead creating some generic base classes. An example of which is below:
As you can see we have now been able to make BasePayload
protected. ApuxActionResult
shared a similar fate. Now we have a tightly encapsulated action how do we use it?
Usage
We now pass around ApuxActionBase
and ApuxActionResultBase
instead of their interface variant. For example our root dispatcher now looks like:
This change has lead to the removal of an unnecessary generic typing of JToken
on an ApuxAction
when its payload is still not needed. An example can be seen in one of our non-root dispatchers:
When it comes time to use our payload in a strict format it is best to create an explicit action and pass that into our business object code or the layer consuming our ActionHandler
. This leads to more verbose code and also allows actions to be reused internally within the router. An example of such an action might be:
Or alternatively if you have an action that requires no payload you can simply inherit from ApuxActionBase
.
When we want to use our payload we access it the same way as before. ApuxAction<T>
now exposes the payload for us while still hiding the base payload. For example we might see the following in our program:
You might have also noticed one other handy function, namely .ToApuxActionResult()
. This is a new extension method found in the namespace Apux.Extensions
which will cast any object into an ApuxActionResult<T>
for you to save making overly verbose strongly typed returns which can get very long. (You’re welcome.)
So there you have it, if you want to upgrade and experience the power of all 1.21 Gigawatts of Apux 2 simply swap any interface references to the appropriate base abstract class and you are good to go. If you want to try out Apux for the first time I suggest you follow Dan’s original article here and substitute interfaces in the example for the abstract base classes I have mentioned above.