Quit Believing In Framework “Magic”!

Emanuel Trandafir
Level Up Coding
Published in
7 min readNov 28, 2022

Do you really need to use all these frameworks and libraries? Would you know to implement them? Can you remove any of them?

Hibernate’s documentation — Photo by Dollar Gill on Unsplash

1. “Good Chefs Don’t Use Cake Mixes” — Christin Gorman

I recently came across a funny short talk from a JavaZone conference back in 2011. In the recording, Christin Gorman is using a humorous analogy between ready-made cake mixes and Hibernate to show the negative impact of blindly adopting “cool” frameworks and libraries.

Even though I am not as keen to let Hibernate go (just yet), I resonated a lot with the ideas in the video.

For instance, the temptation to learn a new, cool-looking, framework rather than investing some time to solidify the fundamentals.

Or the idea of putting effort into adapting your code to comply with various frameworks and libraries instead of investing that creative energy into solving the actual business problem.

The video is only 9 minutes long and I highly encourage you to watch it:

2 . “Make the Magic Go Away!” — Uncle Bob

Watching Christin’s presentation, the first thing that came to mind was the Make the Magic go awayarticle from Uncle Bob’s blog. In this blog post, Robert C. Martin discusses the same subject from a different perspective.

He starts by explaining how frameworks and libraries are created to supplement shortages in the programming language.

After that, he argues that we, as developers, are in a constant search for the perfectlanguage or framework because “we believe in magic”.

He uses the “magic” metaphor to refer to frameworks and the functionalities they offer. Functionalities we don’t fully understand, we wouldn’t know how to implement ourselves, but take for granted.

Moreover, Uncle Bob says that the only escape is to implement any feature a framework has to offer, before blindly using it. This would “make the magic go away” and help us understand what the problem really is, what the library does, and will help us decide if we really need it.

Photo by Danilo D'Agostino on Unsplash

3. My Personal Take

These two speeches really got me thinking. I started thinking about all the different libraries and frameworks I’m using. Can I remove any of them? If not, why?

After some time, I came to the conclusion that the decision doesn’t have to be so radical. In programming terms, it doesn’t have to be a “boolean” value.

For instance, even if it’s not possible (or we don’t want) to completely let go of a library, we can still limit its usage to the features that bring the most value.

Moreover, I came up with four questions that can guide my decision on whether I should use a feature of a library or framework. Ladies and gentlemen, allow me to present you the “M.U.L.E.” questions:

Photo by Laura Nyhuis on Unsplash

Magic: How much magic does it bring?
Can you understand what it does? Even without having prior experience with the library? Even without reading the documentation? Would you be able to implement it yourself?

Utility: How useful is it?
Is it implementing something very complex? Is it helping you to avoid writing boilerplate code? Does it have better performance? If you decided to use this tool in the first place, it probably has something to offer.

Layer: In which layer of your system is it?
The closer it is to your domain layer, the more reluctant you should be to add it. In other words, we want to avoid using external libraries or intrusive frameworks inside our domain layer. On the other hand, when it comes to the “infrastructure” layer, we’ll be less rigorous. For instance, you wouldn’t want to build from scratch an OpenAPI/Swagger-UI web page.

Errors: How error-prone is it?
Is it easy to make mistakes with it? Is it making your code harder to test?

4. Code Examples

I hope you’ve made it this far. It is now time to do what we enjoy the most and start looking into some code snippets.

For this section, I picked three popular Java libraries/frameworks. For each of them, we’ll compare it to a potential plain Java alternative and evaluate it using the M.U.L.E questions.

4.1. MapStruct

MapStruct is a Java library that can be used to generate Mapper objects with minimal configuration. If the fields from the two objects have the same name and type, they will be mapped automatically. Otherwise, an additional annotation will be needed.

@Mapper(componentModel = "spring")
public interface EmployeeMapstructMapper {

@Mapping(target="employeeId", source="employee.id")
@Mapping(target="employeeName", source="employee.name")
EmployeeDto toDto(Employee employee);

@Mapping(target="id", source="dto.employeeId")
@Mapping(target="name", source="dto.employeeName")
Employee fromDto(EmployeeDto dto);
}
  • Magic: I would say the mapping from the code snippet is pretty straightforward. I would probably not know what componentModel = “spring” does without previous experience.
  • Utility: Mapstruct allows us to avoid writing boilerplate code by automatically mapping the fields if they have the same name and type.
  • Layer: We will use the mappers outside of our domain layer.
  • Errors: If we decide to rename one of the fields from our domain model (for example, the “department”) we can potentially forget to update all related mappers and add a @Mapping annotation to specify the new names.
public class EmployeeMapper { 
public static EmployeeDto toDto(Employee employee) {
if(isNull(employee)) {
return null;
}
EmployeeDto dto = new EmployeeDto();
dto.setEmployeeId(employee.getId());
dto.setEmployeeName(employee.getName());
dto.setDepartment(employee.getDepartment());
return dto;
}

public static Employee fromDto(EmployeeDto dto) {
if(isNull(dto)) {
return null;
}
Employee employee = new Employee();
employee.setId(dto.getEmployeeId());
employee.setName(dto.getEmployeeName());
employee.setDepartment(dto.getDepartment());
return employee;
}
}

For big models where most of the fields coincide, MapStruct can come in handy.

Though, for more complex scenarios (for instance, mapping nested objects) the library will bring too much magic for my taste.

4.2. Spring Custom Validator

Let’s take a look at the recommended way of creating a custom validation with Spring. This will usually be done through a new custom annotation and a ConstraintValidator implementation for it.

Let’s see how it looks and compare it to its plain Java alternative:

@Documented
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPhoneNumber {
String message() default "Invalid phone number";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}

public class PhoneNumberValidator implements ConstraintValidator<ValidPhoneNumber, String> {
@Override
public boolean isValid(String phoneNumbe, ConstraintValidatorContext cxt) {
return phoneNumber != null
&& phoneNumber.matches("[0-9]+")
&& (phoneNumber.length() > 8)
&& (phoneNumber.length() < 14);
}
}

public class Employee {
private String name;
private Long id;
@ValidPhoneNumber
private String phoneNumber;

// all arguments constructor
}
  • Magic: I would say it’s quite a lot of “magic” here! I believe it can be pretty hard to find the implementation of @ValidPhoneNumber. And, perhaps more important, it can be hard to determine when and if this validation will be performed.
  • Utility: The custom constraints do not add any extra value in this context, compared to the plain Java solution.
  • Layer: If the Employee class is part of the domain layer, the plain Java version is much better. Using the self-validating constructor, we can ensure that we’ll always have objects that are valid from a business perspective.
  • Errors: While you can never be wrong with the self-validating constructor, the Spring constraints are only evaluated inside the Spring context.
public class PhoneNumber {
private final String value;

public PhoneNumber(String value) {
if (value != null
&& value.matches("[0-9]+")
&& (value.length() > 8)
&& (value.length() < 14)) {
throw new IllegalArgumentException("invalid Phone Number!");
}
this.value = value;
}

// getter for the 'value' field
}


public class Employee {
private final Long id;
private final String name;
private final PhoneNumber phoneNumber;

// all arguments constructor
}

Furthermore, not relying on Spring’s validation forced us to come up with a nice creative solution and enrich our domain model.

4.3. Hibernate

It is time to talk about the elephant in the room. And, when I say elephant, I really mean it: Hibernate is huge!

Even though — most of the time — the JPA @Entities are living inside our core domain, we can treat the database, as Uncle Bob is suggesting, as a detail, as a peripheric component.

It is true that Hibernate is not always generating the best SQL. But, in my opinion, it has a lot more to offer. For instance, I wouldn’t want to implement features such as lazy loading or transactionality: they are not directly related to the business problem I’m trying to solve.

Personally, I believe the “utility” dimension of Hibernate is compensating for the “magic” it brings.

Thank You!

Thanks for reading the article and please let me know what you think! Any feedback is welcome.

If you want to read more about clean code, design, unit testing, functional programming, and many others, make sure to check out my other articles.

If you like my content, consider following or subscribing to the email list.

Finally, if you consider becoming a Medium member and supporting my blog, here’s my referral.

Happy Coding!

Written by Emanuel Trandafir

Hi, I'm Emanuel Trandafir, a Java developer from Romania. I have a strong passion for clean code, software design, and unit testing.

Responses (1)

Write a response