digraph G { ":pedestal" -> ":route-source"; ":route-source" -> ":handler/get-greeting" [label=":get-greeting"]; ":handler/get-greeting" -> ":greeter"; }
As your Component-based Pedestal application grows, you might see some growing pains. The theory and practice of Component is that each component gets exactly the dependencies it specifically needs … no more.
And yet, we have this :components component that, in a fully-featured application, can be expected to grow into a big grab-bag of dependencies. This is a "Big Ball of Mud" - a design antipattern that Component was created to avoid.
Pedestal has optional capabilities to define interceptors and handlers as components, each with individual dependencies. By embracing this approach, we end up with a more focused system map:
digraph G { ":pedestal" -> ":route-source"; ":route-source" -> ":handler/get-greeting" [label=":get-greeting"]; ":handler/get-greeting" -> ":greeter"; }
Again, with this tiny toy of an application, the structure is quite flat.
Before we get started on how this is implemented, there are some tradeoffs to consider:
Greater complexity: more components and more dependencies
Reloading is for functions not objects
Debugging into methods is harder [1]
To expand on that reloading issue: when you reload namespace changes, everything does get replaced. For functions, the fully qualified function name will be shared between the old code and the new.
For Clojure records, after reloading namespaces, the old record instance (in the system map) will still be in place, with the old JVM class for that instance. So, changing the implementation of a method of a record will not see any effect, at least until the system map is rebuilt.
So, keep those concerns in mind while we describe the revised application. This time, we can build from the bottom up, starting with the greeter component.
link:example$component2/src/app/components/greeter.clj[role=include]
This namespace is unchanged from the prior guide.
A new namespace, for components that are interceptors or handlers, has been created.
link:example$component2/src/app/components/handlers.clj[role=include]
1 | api:definterceptor[] extends the behavior of clj:defrecord[]. |
2 | As with a record, we start with fields of the record. |
3 | The handle method is allowed and definterceptor automatically adds the correspondng protocol. |
4 | The implementation can directly reference the greeter field. |
5 | It’s always good form to provide a function to create a new component instance. |
The definterceptor
macro streamlines the process of creating a record type for a component; it automatically adds the protocol (api:Handler[],
api:OnEnter[], api:OnLeave[], or api:OnError[]).
It also quietly adds an implementation of api:IntoInterceptor[ns=io.pedestal.interceptor].
Next up the component heirarchy is the component used to generate the application’s routes. It depends on the handlers and other interceptor components that are referenced in the routes.
link:example$component2/src/app/routes.clj[role=include]
1 | Amazingly, no dependencies are needed for this namespace. |
2 | The routes function is provided with the RoutesSource component and can extract the get-greeting dependency. |
3 | The :route-name option is now omitted, and the name of the interceptor is used as the route name. |
link:example$component2/src/app/pedestal.clj[role=include]
1 | The component has a dependency on route-source , and manages the connector field. |
2 | This is where the RouteSource component is used. |
This all comes together in the app.system
namespace where all components
and dependencies get declared:
link:example$component2/src/app/system.clj[role=include]
1 | A map can be used when the local field does not match the system map key. |
All the other namespaces (primariy, the tests) are unchanged between the two versions of the application.
In the guide we extended the prior application to fully leverage the capabilities
of the Component library. We demonstrated how the definterceptor
macro streamlines
creating components that acts as both components and interceptors (or handlers), and we saw how each component had only explicit dependencies on exactly what
other components it directly interacts with.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close