What is a middleware and why is it good for me?¶
TL;DR¶
You can extract the cross-cutting concerns of your app (like logging) and put them into Middlewares
that can be used as generic plugins instead of copy-pasting the same solution over and over and polluting your code.
The big picture¶
You can picture this like a wrapped input:
-
No middleware:
Source -> Input
-
Adding a layer of middleware:
Source -> Middleware(Input)
-
Adding n layers of middleware:
Source -> Middleware(Middleware(...Middleware(Input)...))
Every layer of Middleware
forwards its input to its wrapped inner core (whether its the actual Input
, or just another layer of Middleware
).
Whether or not an Input
is wrapped in Middlewares
is unknown to the Source
.
The base idea¶
Consider the Consumer<T>
interface:
package io.reactivex.functions;
/**
* A functional interface (callback) that accepts a single value.
* @param <T> the value type
*/
public interface Consumer<T> {
/**
* Consume the given value.
* @param t the value
* @throws Exception on error
*/
void accept(T t) throws Exception;
}
We can easily create a wrapper layer around it:
abstract class Middleware<T>(
private val wrapped: Consumer<T>
) : Consumer<T> {
override fun accept(t: T) {
// do whatever you want before passing it to wrapped...
wrapped.accept(t)
// ... or after it
// ... or even modify what's passed down to wrapped
}
}
Let's play around with it and add some simple implementations:
class HelloMiddleware(
private val wrapped: Consumer<String>
) : Middleware<String>(wrapped) {
override fun accept(t: String) {
wrapped.accept("Hello $t")
}
}
class ReverserMiddleware(
private val wrapped: Consumer<String>
) : Middleware<String>(wrapped) {
override fun accept(t: String) {
wrapped.accept(t.reversed())
}
}
class LoggingMiddleware<T>(
private val wrapped: Consumer<T>,
private val logger: (String) -> Unit
) : Middleware<T>(wrapped) {
override fun accept(t: T) {
logger.invoke("LoggingMiddleware: element - $t")
wrapped.accept(t)
}
}
Now we can compose them and take them for a test ride:
val target: Consumer<String> = TODO()
val wrappedWithMiddlewares: Consumer<String> =
ReverserMiddleware(
HelloMiddleware(
LoggingMiddleware(
{ System.out.println(it) },
target
)
)
)
wrappedWithMiddlewares.accept("MVICore")
// Should output to console:
// "LoggingMiddleware: element - Hello erocIVM!"
Explanation: the order in which an element is passing through the Middleware
layers is from the outermost -> inwards. In the above example this means that the passed in string:
- gets reversed
- gets decorated with Hello %s!
- gets logged