How to cook reactive programming. Part 3: Modularization.

Maxim Smirnov
12 min readJun 22, 2020

--

Last time we were talking about different types of Side Effects, and moreover I showed you some libraries which support Unidirectional data flow approaches. I can certainly say if you’ve already read all the previous articles you now have a clear idea of how reactive programming works, and how to build a unidirectional architecture by yourself.

I would highly recommend firstly reading at least the two previous articles. However, if you’re not familiar with frameworks such as RxSwift or Combine, or reactive programming in general, I’d suggest reading my first article as well.

Thousands of kudos to Dave Phillips. He really helped with this article!

Back to the future

I’ve finished all my articles already. And now we can go back to the future 🏎…

  1. What is Reactive Programming? iOS Edition
  2. How to cook reactive programming. Part 1: Unidirectional architectures introduction.
  3. How to cook reactive programming. Part 2: Side effects.
  4. How to cook reactive programming. Part 3: Modularization.
  5. How to cook reactive programming. Part 4: Testing.

Intro

As I mentioned in my previous article, we live in a world where applications are not one button flashlight apps anymore. We have teams of more than ten people. And if you noticed, the main idea of Unidirectional architecture is to keep all data in one place. I bet, if you start with this approach, you’ll end up with a huge State and Reducer if not at the end of the week, then certainly by the end of the month. You may wonder how it's possible to separate the Unidirectional approach on different modules when the main idea is to keep everything in one place. That’s exactly what I'm going to show you today.

Today I will show you two approaches for Unidirectional architecturemodularization. I picked these two not because there are not more out there - you can easily find them if you search the internet. I picked them because they are opposite to one another. As a starting point, I picked a module that I want to use for experiments from the previous chapter.

But this time we need an app, where all the magic will happen. I’d like to keep it as simple as possible.

Imagine you have a small module like I’ve shown you recently, which can download news titles. Let’s just name it News. And you have the app itself, let's just call it App. Quite a simple module I’d say, but this time we need a User who’s shared within the whole app. Moreover, let's add a situation when the main app should know about the loading step from the news module, for example, it has another page, which depends on the news titles. So, let's start with the first approach on how to connect it to the main app. A list of requirements would look like this:

  • The App needs to know about all changes in the News
  • The News needs to know about all changes in the part of the App (User)

I think these requirements are simple and are enough for showing modularization concepts. So, let’s start with the first approach.

Composable Reducers

If you read my previous article, I bet you’ve already understood what architecture I’m going to talk about. Composable Architecture provides a really good way for modularization. What do they do?

The main idea of the modularization with the composable approach is to store every Eventand every State in the App module. Let's change the app a little bit to conform to the new requirement.

So far, I’ve just put everything inside the App itself. We’ll talk about problems with this approach a little bit later. For now, we need to proceed and solve the next problem. How would this system work? Maybe you've noticed that I omitted reducer for the new app variation. I did this on purpose because the entire work which should be done exists in the reducer itself.

Let’s try to solve this problem as it stands with building a new reducer.

It’s a little bit clumsy, but it works. However, I’ve just generated another problem. NewsStateis optional and should be created somewhere. Let's try to create it the first time when .newsEvents have appeared. The final reducer would look like this:

Now the system works. However, it doesn’t look like a modularization at all. All actions happen inside one function in the main application. Let’s try to fix it a little bit. We’ve already had a reduce function for the News module. I want to try to adopt it as is to the previously implemented reducer.

Now we have the composition of the most primitive function. It looks better, but it’s still not what we would expect from modularization. Could we somehow improve this composition? Of course we can — in the end, I’m not inventing here anything.

I’d suggest starting with the functions declaration first. What do we already have? We have two reducers, which work like one reducer. What does that mean? It means we want a function which could compose two separate reducers into one. So, for the first step, we need a reducer declaration.

Just a type alias for the function, which takes State and Event as the input and provides a result state as an output. Ok, so there’s nothing new here. For the next step let's make a declaration for the combine function, which combines multiple reducers into one.

Things are progressing easily so far. Now with these declarations, we’ll write a realization for the combine function. We just need to chain every reducer with the provided state and event.

This function could be realized with a simple “for” loop, however, the usage of the reducefunction inside our combine looks more symbolic, doesn't it? Let's try out what we've done so far.

What have I just done? I’ve created two reducers, combined them into one result reducer, and performed two mutations. As a result you can see that two completely separated reducers mutate one state, which is a proof of concept. However, I created another problem, and I’ve started to have the feeling that I’m only creating problems, not solving them. The combinefunction can work only over one type of state - AppState- and one type of event - AppEvent. We have the News module, which has different state and event types. How could this situation be resolved? Maybe you've already guessed, according to my first iterations, that NewsState, now a part of AppState and NewsEvent, is a part of AppEvent and we could apply some conversions to fulfill combine function requirements. But what should we do exactly? We need to convert NewsReducer into AppReducer.

NewsReducer has this declaration:

And AppReducer this:

I want to split this task into two smaller ones. Let’s start with transforming

into

As usual I want to start with a function definition:

Because Reducer is a function itself, we need to build a function inside another function 🤯. The first step will look like this:

To build a function which uses AppEvent and AppState we have NewsState and AppEvent. What can we do here? Because we need to put NewsState inside NewsReducer we need to somehow transform AppState into NewsState:

We’re Halfway there, but it’s not enough, as we’ve got an error

Cannot convert return expression of type ‘NewsState’ to return type ‘AppState’

It means that we need somehow to transform the news reducer result in AppState. We already know that NewsState is a part of the AppState, so it shouldn’t be so hard.

It will work, I can assure you. However, it doesn’t look generic enough for usage for different applications with different state interfaces. So let’s fix it.

Much better, but we could refactor it a little bit more with a KeyPath usage, and decrease the number of input parameters of the function, so let's try it out.

That’s what I’m talking about. Let’s move forward, we still have unfinished business according to events. The desired transform function will look like this:

Let’s start with an update to the existing one:

This code will show another error:

Cannot convert value of type ‘AppEvent’ to expected argument type ‘NewsEvent’

As far as you can see a Swift compiler really helps in these kinds of things, as throughout I’m just doing whatever it says to me. And now we need to convert AppEvent into NewsEvent. We've already made one as a part of another. Now we should make one small addition to make things easier.

We have a newsEvent property, which allows us to easily convert AppEvent into NewsEvent if it's possible. I'm going to add this update and fix the error.

In this case, if the app event was a news event we will perform a news reducer. However, the same small change should be added to make the transform function more generic. What have we done so far? We converted AppEvent into NewsEvent, so should we just add a conversion function as another parameter of the transform function?

Perfect! Now we have a transform function, which will really help with the reducers composition. Shall, we add the final step to make transform and combine into generic functions?

Voila! So it’s a small change, but we can use these functions for every reducer right now. Let’s test them with our previous test case.

The output is the same as on the previous occasion, however we now have a fully generic approach to achieve this. And that’s it, we’ve achieved a fully generic way of Unidirectional architecture modularization. It's actually quite a straightforward way to modularize your system. Before we move forward to another modularization approach, let's talk a little bit about the pros and cons.

Pros 👍:
The true way of Unidirectional data flow. You don't lose any of the advantages of the unidirectional approach and as far as you can see the main mantra still applies - everything is in the one place, everything works in the one direction, everything is consistent. It goes from the previous point, there's no way - unless of course you deliberately make one - when you have for instance different users in different modules.

Cons 👎:
Not true module separation. To achieve this modularization approach you need to reveal the State and Event of the underlying module to the module where you attach things. It shouldn't be a big problem if all your modules work together only in one project or several affiliated projects. However, it could cause some problems in two cases. The first case is if you just have started to use unidirectional approaches in your codebase. I bet you want to isolate and reuse modules in this case, not connect them in one place. In the second case when you work over a framework, you shouldn’t expect your customers to adopt a way of modularization, which I've just shown to you.

As I’ve mentioned before, what I’ve shown you I took from the pointfree channel. I’ve tried to make things a little bit different. They separated the app by modules, but I attached the existing module to the app. It’s not a big difference, but if you want to go deeper consider watching their videos as well. Also, they’ve explained a lot of functional programming concepts, which we used to achieve this modularization approach.

Fully separated approach

Do you remember what were the problems of the previous approach? That it’s hard to use in isolation and you have to reveal a state and event for an upper module. With this approach, you can solve these problems.

For this time we need to remember about Store.

Why do we need Store here so badly? Because Store is an entry point of the whole module creation. We actually need it for Composable Reducers, but it’s not so bad and I wanted to save a little bit of your time.

So, how do we achieve modularization? The answer is actually more straightforward or even easier than with a previous approach. We just need to provide a way of updating information from the outside world and provide a way to notify changes from the child module.

With any reactive framework, it’s a super-easy task. So, let’s implement it in one step.

What are all the interesting things that happened in the Store? I've just added two publishers Input and Output. With input, I can observe what happened in the outside world of the module and update data, according to the input event. With output, I can cut a piece of the state, which could be interesting in the outside world. Also, there’s one small addition because the sink method produces Cancellable type, so we need to somehow utilize this sinksubscription. I've attached it to the lifecycle of the view controller however, it could be done in different ways since unidirectional architecture could be used not only in the partnership with a view.

The second approach part ended up much shorter than the previous one. I hope it’s because this approach is far easier to understand and to implement, not because I’m too lazy to write it step by step. Now let’s talk about the pros and cons of this approach.

Pros 👍:
True modularization. An application doesn’t depend on the underlying module realization. If you want to try unidirectional approaches, it’s a way to adopt this as an only part of the project. Also, it’s suitable for SDKs creation.

Cons 👎:
It’s too easy to create the not true unidirectional state, because modules could have their own independent states and the whole app state could be inconsistent. Because it’s a state-based system, sometimes you have to almost manually reset a state. Imagine the situation, that you need to open another scene from your child module. You have something like sceneForOpen property in the child module. The main module should send something, like sceneWasShown, back to the child module for clearing sceneForOpen property. Otherwise, if you go back and want to open the same page again nothing would happen, because the child's state hasn't been changed.

What about Side Effects?

Side effects realization with modularization highly depend on the way you implement these side effects.

With the second fully separated approach there’s no difference in handling side effects at all. You have a fully separated module with its own side effects, when you create this module side effects will work, as I’ve shown in the previous article.

On the other hand, we have an approach with composable reducers. And thus I don’t want to repeat the pointfree.co Effects approach to modularization. You can easily find an explanation in their videos or on github.

However, I want to show you at least something. Composable architecture has its effects system, but imagine the situation when you have composable reducers as a modularization approach and query-based side effects. Here’s some code, showing how you could create a child store (child module) from the main store (main module). The main idea here is that every module has its own store, but every child module has a piece of this store from the main module.

In this case, we have a scope function which transforms the main store to the child one. Here SideEffect<LocalState, LocalEvent> is a pairing of query and action. The main idea of this approach is that while we create a child module, we inject side effects from it to the scope function. It gives us a way to construct modules with independent side effects. Every other action which is occurring in this function is mostly about main - child communication. It's necessary because we want to reflect changes from the main module inside the child one and visa versa.

There’s nothing hard to understand if you’ve already got how to handle modularization in general, trust me.

Outro

As usual, there’s no silver bullet for modularization. You can decide for yourself what to use. So far, you’ve become familiar with how to work and even how to build your own reactive framework and unidirectional architecture. Actually you know most of these things already. However, in my opinion, there’s still one important topic left — testing. Unidirectionalarchitectures give us a super elegant way of testing your code. How to do the testing I will show you in the next article. So, let's keep in touch!

If you liked this article don’t forget to crash a clap button. Moreover, you can do it 50 times, for you, it’s super easy, but it will be really helpful for me.

If you don’t want to lose any new articles subscribe to my twitter account

--

--