Copyright (c) 2025 Roman Vidayko
www.linkedin.com/in/roman-vidayko
๐๐ก๐ง๐ฅ๐ข
I decided to share my thoughts on how to make a software developer's life simpler and more efficient. It is not my intention to provide all-in-one silver bullets, but rather to offer some mindful ideas with supporting code snippets that provide fundamental understanding.
๐๐ก๐๐๐ง๐
Being developing complex software, you've probably encountered the routine of converting a data model from local data types to remote ones and vice versa. Have you ever wondered what ideal data mapping should look like?
Here, I am going to introduce a solution that uses the power of Java and is based on the fundamental principles of Design Patterns. But let's first take a look at how it is usually done to understand the pros and cons.
๐๐ข๐ ๐ ๐ข๐ก ๐ฃ๐ฅ๐๐๐ง๐๐๐
Here are a couple of samples sketched out that are present in any project, like the tentacles of the Lernaean Hydraโno matter how many you cut off, new ones will still grow.
๐ข๐ฝ๐๐ถ๐ผ๐ป ๐ญ: ๐๐ถ๐ฟ๐ฒ๐ฐ๐ ๐ฐ๐ผ๐ป๐๐ฒ๐ฟ๐๐ถ๐ผ๐ป
๐๐๐๐๐๐๐๐ข๐๐๐ท ๐๐๐๐๐๐๐๐ข๐๐๐ท = ๐๐๐ ๐๐๐๐๐๐๐๐ข๐๐๐ท(
๐๐๐๐๐๐๐ข๐๐๐ท.๐๐๐๐๐๐ท(),
...
๐๐๐๐๐๐๐ข๐๐๐ท.๐๐๐๐๐๐ฝ());
Pอrอoอsอ: ๐๐ต ๐ช๐ด ๐ด๐ช๐ฎ๐ฑ๐ญ๐ฆ, ๐ข๐ฏ๐ฅ ๐บ๐ฐ๐ถ ๐ฅ๐ฐ๐ฏโ๐ต ๐ณ๐ฆ๐ข๐ญ๐ญ๐บ ๐ฏ๐ฆ๐ฆ๐ฅ ๐ต๐ฐ ๐ฌ๐ฏ๐ฐ๐ธ ๐๐ข๐ท๐ข ๐ต๐ฐ ๐ช๐ฎ๐ฑ๐ญ๐ฆ๐ฎ๐ฆ๐ฏ๐ต ๐ต๐ฉ๐ช๐ด! ๐ฅด
Cอoอnอsอ: ๐๐ฉ๐ฆ ๐ฎ๐ฆ๐ต๐ฉ๐ฐ๐ฅ ๐ฃ๐ฐ๐ฅ๐บ ๐ต๐ฉ๐ข๐ต ๐ค๐ฐ๐ฏ๐ต๐ข๐ช๐ฏ๐ด ๐ช๐ต ๐ช๐ด ๐ด๐ฑ๐ฐ๐ช๐ญ๐ฆ๐ฅ ๐ธ๐ช๐ต๐ฉ ๐ญ๐ฐ๐จ๐ช๐ค ๐ต๐ฉ๐ข๐ต ๐ช๐ด ๐ฐ๐ถ๐ต ๐ฐ๐ง ๐ต๐ฉ๐ฆ ๐ฎ๐ฆ๐ต๐ฉ๐ฐ๐ฅ'๐ด ๐ณ๐ฆ๐ด๐ฑ๐ฐ๐ฏ๐ด๐ช๐ฃ๐ช๐ญ๐ช๐ต๐บ ๐ด๐ค๐ฐ๐ฑ๐ฆ.
๐ข๐ฝ๐๐ถ๐ผ๐ป ๐ฎ: ๐จ๐๐ฎ๐ด๐ฒ ๐ผ๐ณ ๐ ๐ฎ๐ฝ๐ฝ๐ฒ๐ฟ ๐จ๐๐ถ๐น๐ถ๐๐ ๐๐น๐ฎ๐๐
๐๐๐๐๐ ๐๐๐๐๐๐๐๐ข๐๐๐ท ๐๐๐๐๐๐๐๐ข๐๐๐ท = ๐๐๐๐๐๐๐ผ๐๐๐๐๐.๐๐๐๐๐๐๐๐๐๐๐๐๐ข๐๐๐ท(๐๐๐๐๐๐๐ข๐๐๐ท);
...
๐๐๐๐๐ ๐๐๐๐๐๐๐ผ๐๐๐๐๐ {
๐๐๐๐๐๐ ๐๐๐๐๐๐ ๐๐๐๐๐๐๐๐ข๐๐๐ท ๐๐๐๐๐๐๐๐๐๐๐๐๐ข๐๐๐ท(๐ป๐๐๐๐๐๐ข๐๐๐ท ๐๐๐๐๐๐๐ข๐๐๐ท){
๐๐๐๐๐๐ ๐๐๐ ๐๐๐๐๐๐๐๐ข๐๐๐ท(
...,
๐๐๐๐๐๐๐๐๐๐๐๐๐ข๐๐๐ธ(๐๐๐๐๐๐๐ข๐๐๐ท));
}
๐๐๐๐๐๐ ๐๐๐๐๐๐ ๐๐๐๐๐๐๐๐ข๐๐๐ธ ๐๐๐๐๐๐๐๐๐๐๐๐๐ข๐๐๐ธ(๐ป๐๐๐๐๐๐ข๐๐๐ท ๐๐๐๐๐๐๐ข๐๐๐ท){
๐๐๐๐๐๐ ๐๐๐ ๐๐๐๐๐๐๐๐ข๐๐๐ธ(
...
}
}
Pอrอoอsอ: ๐๐ฐ๐ต ๐ณ๐ช๐ฅ ๐ฐ๐ง ๐ต๐ฉ๐ฆ ๐ฎ๐ข๐ฑ๐ฑ๐ช๐ฏ๐จ ๐ญ๐ฐ๐จ๐ช๐ค ๐ง๐ช๐น๐ช๐ฏ๐จ ๐ต๐ฉ๐ฆ ๐ฎ๐ฆ๐ต๐ฉ๐ฐ๐ฅ'๐ด ๐ด๐ช๐ฏ๐จ๐ญ๐ฆ ๐ณ๐ฆ๐ด๐ฑ๐ฐ๐ฏ๐ด๐ช๐ฃ๐ช๐ญ๐ช๐ต๐บ.
Cอoอnอsอ: ๐๐ช๐จ๐ฉ๐ต ๐ค๐ฐ๐ถ๐ฑ๐ญ๐ช๐ฏ๐จ ๐ธ๐ช๐ต๐ฉ ๐ค๐ฐ๐ฏ๐ด๐ถ๐ฎ๐ฆ๐ณ๐ด, ๐ช๐ฏ๐ข๐ฃ๐ช๐ญ๐ช๐ต๐บ ๐ต๐ฐ ๐ค๐ถ๐ด๐ต๐ฐ๐ฎ๐ช๐ป๐ฆ ๐ฎ๐ฆ๐ต๐ฉ๐ฐ๐ฅ๐ด, ๐ฏ๐ข๐ฎ๐ฆ๐ด๐ฑ๐ข๐ค๐ฆ ๐ฑ๐ฐ๐ญ๐ญ๐ถ๐ต๐ช๐ฐ๐ฏ, ๐ญ๐ฐ๐ธ ๐ฎ๐ข๐ช๐ฏ๐ต๐ข๐ช๐ฏ๐ข๐ฃ๐ช๐ญ๐ช๐ต๐บ ๐ฐ๐ง ๐ถ๐ต๐ช๐ญ๐ช๐ต๐บ ๐ค๐ญ๐ข๐ด๐ด๐ฆ๐ด, ๐ข๐ฏ๐ฅ ๐ฑ๐ฐ๐ต๐ฆ๐ฏ๐ต๐ช๐ข๐ญ ๐ฎ๐ฆ๐ฎ๐ฐ๐ณ๐บ ๐ญ๐ฆ๐ข๐ฌ๐ด ๐ธ๐ฉ๐ฆ๐ฏ ๐ต๐ฉ๐ฆ ๐ฏ๐ถ๐ฎ๐ฃ๐ฆ๐ณ ๐ฐ๐ง ๐ด๐ต๐ข๐ต๐ช๐ค ๐ฎ๐ฆ๐ต๐ฉ๐ฐ๐ฅ๐ด ๐จ๐ณ๐ฐ๐ธ๐ด ๐ถ๐ฏ๐ค๐ฐ๐ฏ๐ต๐ณ๐ฐ๐ญ๐ญ๐ข๐ฃ๐ญ๐บ.
๐ข ๐๐ฅ๐๐ฉ๐ ๐ก๐๐ช ๐ฃ๐๐๐ก๐๐๐ฆ๐ฆ ๐ ๐๐ฃ๐ฃ๐๐ก๐
Thinking about this, I was looking for a solution that has a strong, clear pattern with a minimal number of checkpoints, something simple to understand and implement. It should be so clear and simple that my peers will be inclined to follow it, rather than improvising with rudimentary hacks.
The basis of this solution involves two design patterns: ๐๐ต๐ณ๐ข๐ต๐ฆ๐จ๐บ and ๐๐ฆ๐ณ๐ท๐ช๐ค๐ฆ ๐๐ฐ๐ค๐ข๐ต๐ฐ๐ณ. The ๐๐ต๐ณ๐ข๐ต๐ฆ๐จ๐ช๐ฆ๐ด will perform the mapping, and the ๐๐ฆ๐ณ๐ท๐ช๐ค๐ฆ ๐๐ฐ๐ค๐ข๐ต๐ฐ๐ณ will be responsible for selecting the right mappers. Additionally, the ๐๐ฐ๐ฏ๐ต๐ฆ๐น๐ต ๐๐ฃ๐ซ๐ฆ๐ค๐ต pattern is involved. This is because local and remote data sometimes only partially overlap, and the ๐๐ฐ๐ฏ๐ต๐ฆ๐น๐ต ๐๐ฃ๐ซ๐ฆ๐ค๐ต is designed to bring together all the data that needs to be contained by a target data type.
Thatโs a lot of words, letโs jump into fฬถiฬถgฬถhฬถtฬถ coding!
๐๐ข๐ช ๐ง๐ข ๐จ๐ฆ๐
// ๐ผ๐๐๐๐๐๐ ๐๐ ๐๐๐๐๐๐ ๐ฐ๐ฟ๐ธ ๐๐๐๐๐๐ข๐๐
๐๐๐๐๐ ๐๐ข๐๐๐ท ๐๐ข๐๐๐ท = ๐๐๐๐๐๐๐ป๐๐๐๐๐๐.๐๐๐(๐๐๐ ๐๐ข๐๐๐ท๐ฒ๐๐๐๐๐ก๐(๐๐ข๐๐๐ท๐ฒ๐๐๐๐๐ก๐๐ณ๐๐๐));
// ๐ฒ๐๐๐๐๐๐ ๐๐๐๐๐๐ ๐ฐ๐ฟ๐ธ
๐๐๐๐๐ ๐๐ข๐๐๐ธ ๐๐ข๐๐๐ธ = ๐๐๐๐๐๐๐ฐ๐๐๐ฐ๐๐๐๐๐๐.๐๐๐๐๐๐๐๐๐๐ณ๐๐๐(๐๐ข๐๐๐ท);
// ๐ผ๐๐๐๐๐๐ ๐๐ ๐๐๐๐๐ ๐๐๐๐๐๐ข๐๐ ๐๐๐๐ ๐๐๐๐๐๐ ๐ฐ๐ฟ๐ธ ๐๐๐๐๐๐ข๐๐
๐๐ข๐๐๐น ๐๐ข๐๐๐น = ๐๐๐๐๐๐๐ป๐๐๐๐๐๐.๐๐๐(๐๐๐ ๐๐ข๐๐๐น๐ฒ๐๐๐๐๐ก๐(๐๐ข๐๐๐ธ));
How do you like the way the new mapping looks? Easy-peasy? Just calling the mapperLocator's single method, and that's it!
Letโs go under the hood and see if the technical side of the solution is just as wonderful?
๐ฆ๐๐ฅ ๐ ๐๐ฃ๐ฃ๐๐ฅ-๐๐ข๐๐๐ง๐ข๐ฅ
๐๐๐๐๐๐ ๐๐๐๐๐ ๐ผ๐๐๐๐๐๐ป๐๐๐๐๐๐ {
๐๐๐๐๐๐๐ ๐๐๐๐๐ ๐ผ๐๐<๐ฒ๐๐๐๐<๐๐๐๐๐๐>, ๐ผ๐๐๐๐๐<? ๐๐ก๐๐๐๐๐ ๐๐๐๐๐๐, ?>> ๐๐๐๐๐๐๐ = ๐๐๐ ๐ท๐๐๐๐ผ๐๐<>();
๐๐๐๐๐๐ ๐ผ๐๐๐๐๐๐ป๐๐๐๐๐๐(๐ป๐๐๐<๐ผ๐๐๐๐๐<? ๐๐ก๐๐๐๐๐ ๐๐๐๐๐๐, ?>> ๐๐๐๐๐๐๐) {
๐๐๐๐๐๐๐.๐๐๐๐๐๐().๐๐๐๐ด๐๐๐(๐๐๐๐::๐๐๐๐๐๐๐๐);
}
๐๐๐๐๐๐๐ ๐๐๐๐ ๐๐๐๐๐๐๐๐(๐ผ๐๐๐๐๐<? ๐๐ก๐๐๐๐๐ ๐๐๐๐๐๐, ?> ๐๐๐๐๐๐){
๐๐๐๐๐๐๐.๐๐๐(๐๐๐๐๐๐๐๐๐๐๐ข๐๐(๐๐๐๐๐๐), ๐๐๐๐๐๐);
}
๐๐๐๐๐๐ <๐> ๐ ๐๐๐(๐๐๐๐๐๐ ๐๐๐๐๐๐) {
๐๐๐๐๐๐ ((๐ผ๐๐๐๐๐<๐๐๐๐๐๐, ๐>) ๐๐๐๐๐๐๐.๐๐๐(๐๐๐๐๐๐.๐๐๐๐ฒ๐๐๐๐())).๐๐๐(๐๐๐๐๐๐);
}
๐๐๐๐๐๐๐ ๐ฒ๐๐๐๐<๐๐๐๐๐๐> ๐๐๐๐๐๐๐๐๐๐๐ข๐๐(๐ผ๐๐๐๐๐<? ๐๐ก๐๐๐๐๐ ๐๐๐๐๐๐, ?> ๐๐๐๐๐๐) {
// ๐๐๐๐ ๐น๐๐๐ ๐๐๐๐๐๐๐๐๐๐ ๐๐๐๐ ๐ ๐๐๐ ๐๐๐๐๐๐๐๐๐๐ ๐๐๐ ๐๐๐๐๐๐ ๐๐๐๐-๐๐ข๐๐ ๐๐๐ ๐ ๐๐๐๐๐๐
}
}
No magic, just an ordinary job. The source types are keys to identify the right mapper. Source is just a marker interface. Simply supply the mapper instances to the constructor, or declare them as beans if it's a Spring application.
๐ง๐๐ ๐ ๐๐ก๐๐ข๐ก๐ฆ
๐๐๐๐๐๐ ๐๐๐๐๐๐๐๐๐ ๐ผ๐๐๐๐๐<๐๐๐๐๐๐, ๐> {
๐ ๐๐๐(๐๐๐๐๐๐ ๐๐๐๐๐๐);
}
๐๐๐๐๐๐ ๐๐๐๐๐ ๐๐ข๐๐๐ธ๐ผ๐๐๐๐๐ ๐๐๐๐๐๐๐๐๐๐ ๐ผ๐๐๐๐๐<๐๐ข๐๐๐ธ๐ฒ๐๐๐๐๐ก๐, ๐๐ข๐๐๐ธ> {
@๐พ๐๐๐๐๐๐๐
๐๐๐๐๐๐ ๐๐ข๐๐๐ธ ๐๐๐(๐๐ข๐๐๐ธ๐ฒ๐๐๐๐๐ก๐ ๐๐๐๐๐๐) {
๐๐๐๐๐๐ ๐๐๐ ๐๐ข๐๐๐ธ(๐๐๐๐๐๐.๐๐๐๐๐๐ท, ... , ๐๐๐๐๐๐.๐๐๐๐๐๐ฝ);
}
}
๐๐๐๐๐๐ ๐๐๐๐๐ ๐๐ข๐๐๐ท๐ผ๐๐๐๐๐ ๐๐๐๐๐๐๐๐๐๐ ๐ผ๐๐๐๐๐<๐๐ข๐๐๐ท๐ฒ๐๐๐๐๐ก๐, ๐๐ข๐๐๐ท> {
@๐ฐ๐๐๐๐ ๐๐๐๐ @๐ป๐๐ฃ๐ข
๐๐๐๐๐๐๐ ๐ผ๐๐๐๐๐๐ป๐๐๐๐๐๐ ๐๐๐๐๐๐๐ป๐๐๐๐๐๐;
@๐พ๐๐๐๐๐๐๐
๐๐๐๐๐๐ ๐๐ข๐๐๐ท ๐๐๐(๐๐ข๐๐๐ท๐ฒ๐๐๐๐๐ก๐ ๐๐๐๐๐๐) {
๐๐๐๐๐ ๐๐ข๐๐๐ธ ๐๐ข๐๐๐ธ = ๐๐๐๐๐๐๐ป๐๐๐๐๐๐.๐๐๐(๐๐๐ ๐๐ข๐๐๐ธ๐ฒ๐๐๐๐๐ก๐(๐๐๐๐๐๐.๐๐๐๐๐๐, ๐๐๐๐๐๐));
๐๐๐๐๐๐ ๐๐๐ ๐๐ข๐๐๐ท(๐๐๐๐๐๐.๐๐๐๐๐๐ท, ... , ๐๐๐๐๐๐.๐๐๐๐๐๐ฝ, ๐๐ข๐๐๐ธ);
}
}
This is the last thing needed to implement. Mappers are just mappers, well distinguished by the MapperLocator. By the way, they allow you to call the MapperLocator from inside for doing nested mapping.
๐ฃ๐ฅ๐ข๐ฆ
Simple to use, easy to increase mapping coverage, loose coupling, atomicity of mappers, easy testing and maintenance, and regular JVM garbage collection is efficient.
๐๐ข๐ก๐ฆ
The entry-level requirements for developers are higher, requiring code reviews and proper test coverage.
๐ฆ๐จ๐ ๐ ๐๐ฅ๐ฌ
What is the way to use this solution once itโs introduced? Simply add a ๐๐ฆ๐ธ๐๐บ๐ฑ๐ฆ๐๐ข๐ฑ๐ฑ๐ฆ๐ณ, a ๐๐ฆ๐ธ๐๐บ๐ฑ๐ฆ๐๐ฐ๐ฏ๐ต๐ฆ๐น๐ต, and supply the new mapper to the ๐๐ข๐ฑ๐ฑ๐ฆ๐ณ๐๐ฐ๐ค๐ข๐ต๐ฐ๐ณ. Then call the new mapper via the mapperLocator's ๐ฎ๐ข๐ฑ() method.
๐๐ณ๐ช๐จ๐ช๐ฏ๐ข๐ญ ๐ข๐ณ๐ต๐ช๐ค๐ญ๐ฆ: https://github.com/roman-vidayko/articles/blob/main/a001/src/content/article_data_mapping.txt
๐๐ต๐ข๐ณ๐ต๐ช๐ฏ๐จ ๐ฑ๐ฐ๐ช๐ฏ๐ต ๐ต๐ฐ ๐ญ๐ฆ๐ข๐ณ๐ฏ ๐ต๐ฉ๐ฆ ๐ค๐ฐ๐ฅ๐ฆ: https://github.com/roman-vidayko/articles/blob/main/a001/src/main/java/com/vidayko/service/CustomerApiService.java#L38-L53
Top comments (0)