Functional Data Mappers

A while ago I wrote a post about the Repository Pattern in which, among the other concepts, I explained the importance of model separation and data mapping.
After reading the comments on my article I was glad to see readers agreeing on the approach and overcoming the general hate over “extra models” and “annoying mappers”.
In the article, I also showed abstractions and generic implementations that can help to remove some boilerplate code.
All of that was achieved using simple OOP concepts but today I want to show you how you can achieve the same using Functional Programming (and with less boilerplate code 🎉).
You can find all the sample code here:
For who doesn’t remember my previous post or didn’t read it yet (now it is a good time for reading it 😇) these are some generic components I used for removing some boilerplate code:
OOP to FP conversion
Since these are all single-method classes the conversion it’s pretty straightforward:
Basically class fields are replaced by higher-order functions and there is no more need for interfaces since function declarations are already an abstraction 😎.
You may now be worried about this change since one of the issues of FP is that dependencies become “explicit” since they appear in the function signature rather than being hidden by an interface and then injected in the constructor of the concrete implementation but don’t worry I’ll show you later a simple workaround 😃.
Let’s now take some real data mappers and convert them using the same approach.
So let’s say these are the models to map:
The OOP way
This is how I would map the above network DTOs into the Domain Models using a standard OOP approach:
The FP way
And this would be the FP translation:
The Repository
Let’s now have a look at this simple AlbumRepository:
You can see that albumDataMapper is doing a great job in abstracting the mapping logic so now we should expect minimum changes:
Almost the same, isn’t it? 😃
If you check the sample project you’ll see that the unit tests look similar as well.
The Injection
If you look at the snippets above, none of the functions has this signature:
fun mapAlbumDTO(networkAlbum: NetworkAlbum): Album
and all we have is:
fun mapAlbumDto(
input: NetworkAlbum,
mapSongList: (List<NetworkSong>?) -> List<Song>
): Album
We don’t want the Repository to deal with the explicit dependencies of the mapAlbumDto function so this is where the injection comes into play (the workaround I talked about before).
For simplicity, I’ll just use a simple Factory that creates an instance of the Repository (in your project you can replicate the same thing using the tool you prefer: Dagger, Koin, manual DI…).
Factory for the OOP version
Factory for the FP version
As you can see the second Factory is composing all the small functions for generating the final function required by the Repository, this way the Repository stays clean.
The Repository for the OOP version looks leaner than the one for the FP version (this is a downside of my approach) but I don’t think it is a big deal considering that this is just a Factory and all the boilerplate we removed.
Facades
When dealing with bigger Repositories chances are Data Mapping logic will require a lot of dependencies.
If this is the case you are facing, whether you are using the OOP or the FP approach, I suggest you adopt the Facade Pattern.
So if you end up having something like this (which may also potentially grow in the future):
class ARepositoryImpl(
private val apiService: ApiService,
private val mapM1ToM2: (M1) -> M2,
private val mapM1ToM3: (M1) -> M3,
private val mapM2ToM1: (M2) -> M1,
private val mapM3ToM1: (M3) -> M1
): ARepository {//...}
You can simplify this way:
class DataMappersFacade(
val mapM1ToM2: (M1) -> M2,
val mapM1ToM3: (M1) -> M3,
val mapM2ToM1: (M2) -> M1,
val mapM3ToM1: (M3) -> M1
)class ARepositoryImpl(
private val apiService: ApiService,
private val dataMappersFacade: DataMappersFacade
): ARepository {//...}
and access the mappers by doing dataMappersFacade.mapM1ToM2(m1).
The OOP version would require some more boilerplate code since you would need both an interface and a concrete class for the Facade.
This is another advantage of the FP approach 😎.
Last notes
FP and OOP have both advantages and disadvantages, some developers want to see a winner between the 2 but in my opinion, it’s like comparing apples with bananas.
FP works well with the Data Mapping Layer because there are no side effects generated by the mapping so it is simple to keep functions “pure”.
This, however, doesn’t apply to the Repository Layer so better to stick with OOP.
The more tools you have the better it is but be wise and use 🍎 for apple pies and 🍌 for banana pudding.