Flux is a ‘mainstream’ approach of data management for React.js-based views. While I find Flux useful, I don’t think it is always needed — in fact, it can be a bad fit for your project if you follow it blindly. Dan Abramov has some excellent points about the topic and provides some basic guidelines when Flux can be beneficial for you — and I agree with most points he made in his article.
There are alternatives to Flux when it comes to solving data management and achieving maintainable code. Flux is great, but it’s great for use cases Facebook had. It absolutely shines when you have multiple representations of the same data — single source of truth principle helps you avoiding headaches with denormalized state.
Hexagonal Architecture (also called ports and adapters architecture) is an idea about structuring your application around the domain code. The main goal is that your business logic is the most important part of the application — and it should be very loosely coupled with technical details like database or user interface. The rule of thumb is — if you can test it easily with simple assertions, it is nicely written business logic code. In most cases, such business logic is closed in a form of use case — an object describing one business process in your application. Such use case can be named like RegisteringProduct or PayingForOrder — so you can clearly see from name which business process is represented by such use case.
Of course software is all about side effects — you manipulate data to eventually display it on your screen or store it in a database. So having just an use case is great (because you can test early that your business logic works with stubbed data), but to make it work you need more than it.
That’s why you need an abstraction for performing side effect like that. The hexagonal architecture has an idea of ports, which basically represents such side effects. So, you can write something like this:
As you can see, there is a ports dependency which is used to communicate about business situations that should be handled in a more technical way. For example, cartItemAdded can be interesting to handle on the user interface side (increment the cart counter?) and cartItemsConfirmed can be interesting for router to route you out of the current page and go to checkout.
Passing messages through ports object is often replaced with different technical solutions — in the past I’ve used aspect-oriented programming which allowed me to write rules like “after addItemToCart method is called on the useCase object, do something”, where those rules were defined outside the use case. Then I’ve switched to global event bus solution to allow multi-hex communication in an easier way. But there is always an object (often called glue or [more fluxy] dispatcher) which is responsible for defining what to do with outbound messages from the use case and inbound messages to it.
The architecture is named hexagonal because there is an use case (hex) and multiple “sides” (ports) from which messages can go into or out of the use case. Hex (6-sided) is just because it sounds cool — there can be 2, 8 or 42 sides ;).
Let’s see how ports can be implemented:
User interface, or router, or database, or Facebook are so-called adapters. They represent side effects — Facebook adapter can post something on Facebook, User Interface adapter can be a React component. They are concrete implementations — you can see that on the ports level, whether React.js is powering the userInterface adapter is abstracted away. Think about it — actually you can take your use case implementation and drop it to your Node.js backend — just change adapters and reimplement ports and your business logic is reused.
Many use cases, ports and adapters forms an app. Such app is responsible for initialization of your pieces and orchestrating start. Let’s see:
You can imagine that from Product List in the shop you can click the “Ask a question” button and create the customer care ticket this way. There is also the cart management feature. Adapters can be re-used, so here the same user interface is being an adapter connected to two use cases.
This architecture is great if you want to expose your business rules — and it is nearly always the case if your business logic is somehow non-trivial. In terms of data, use case also centralizes your application state — just like stores in Flux do. It also switches your code structure to represent features — having your use cases focused on one user story has an interesting side effect of looking at file names and just knowing what your application has in terms of software functions.
It is better suited for more smart frontends, where you are really doing something on the client side. It is propably an overkill if you happen to just have an user interface which is delegating everything to your backend and updates state by consulting backend.
You can just constraint yourself and use a flavor of hexagonal architecture which is called Flux.
Hexagonal architecture is just more generic Flux
Yeah, it’s not a typo. Flux can be considered a flavor of hexagonal architecture!
- Use case is basically a Store. Methods of an use case is code responsible for processing actions. Such approach is used by Alt library, for example.
- Ports (it’s inbound messages part) is a dispatcher. It is reponsible for communicating use case that some action happened. Just like dispatcher is passing actions to store.
- In Flux there is no concept of outbound methods from ports. Instead, the whole use case (store!) state is passed to all listeners. So there is also no processing of outbound messages — just raw state gets passed in. If you just simplify your ports to one method, notifyAll and pass all state from use case, it’ll be the same.
- There is no concept of action creators in hexagonal architecture, but it can be re-created easily — just instead of publishing an event through an event bus from an adapter, just extract it to a function which adapter will call.
- Actions are implicit. Basically publish arguments are actions — first argument is type, rest of arguments is data passed with an action.
I’ve come to Flux from hexagonal architecture. Approaching Flux knowing hexagonal architecture before allowed me to solve many conceptual problems — like I have no problem with different clients than user interface in Flux loops, or having multiple Flux loops — one for UI and one for backend, sharing store. I like hexagonal approach since it does not need supportive technologies if you don’t want to achieve one-way flow — but one-way flow is also a good practice in hexagonal architecture! It’s called use case roundtrip and it’s basically an idea that adapters should not refer to ports and use case directly — instead it should use different kind of communication like events. Flux just makes it very important piece of architecture!
And, after all — it’s good to know many concepts and approaches when developing your software, right?
- eventing-bus — a dead simple implementation of event bus I’ve used in code snippets.
- Alistair Cockburn’s Post — a totally awesome resource about hexagonal architecture. I’m quite sure (but I can’t find it) Alistair is the one who invented it.
Hexagonal architecture can be a good alternative to Flux — or it can be just an enlightening approach to understand Flux better. I’m very happy with both approaches and I find pros and cons in both of them. I hope the knowledge about this cool, old approach to structuring your application will come in handy.
If you want to talk about architecture (or anything, really :)), just drop a comment. I’m also available on Twitter. If you like my writing, you can also read Arkency Blog (where I write more about Rails) and React Kung Fu (where I write about React.js) where I also publish.
Have a nice day, and good luck with your applications!