Here's what an anonymous spring developer once said 😅:
Everything should be a component.
If you have worked in any Java codebase using the spring framework, maybe you have found classes with some dependencies that can be avoided with minor changes. Let us see in the following example:
In order to do the code less verbose I will use lombok annotations like @AllArgsConstructor
@AllArgsConstructor
@Component
public class AccountAdapter implements AccountPort {
private final RestClient restClient;
private final AccountAdapterTranslator translator;
public Account createAccount(final CreateAccountEntity createAccount) {
CreateAccountRequest request = translator.convertEntityToRequest(createAccount);
Response<CreateAccountResponse> response = restClient.createAccount(request).execute();
return translator.convertResponseToEntity(response);
}
}
@Component
@NoArgsConstructor
public class AccountAdapterTranslator {
public CreateAccountRequest convertEntityToRequest(final CreateAccountEntity createAccount){
return CreateAccountRequest.builder()
.field1(createAccount.getField1())
.field2(createAccount.getField2())
...
.build();
}
public Account convertResponseToEntity(final Response<CreateAccountResponse> response){
return Account.builder()
.fieldX(response.getFieldX())
.fieldY(response.getFieldY())
...
.build();
}
}
I don't know if you see my point here, but why would be the AccountAdapterTranslator class a component?
Let's see how would be a possible unit test of the class AccountAdapter by a Java developer with the spring illness:
@ExtendsWith(MockitoExtension.class)
public class AccountAdapterTest{
@Mock
private RestClient restClient;
@Mock
private AccountAdapterTranslator translator;
@InjectMocks
private AccountAdapter adapter;
@Test
public void testCreateAccountSuccesfully_ShouldReturnAccount(){
//SetUp data
CreateAccountRequest request = CreateAccountRequest.builder().build();
CreateAccountResponse bodyResponse = CreateAccountResponse.builder().build();
RestCall<CreateAccountResponse> restCall = mock(RestCall.class);
Response<CreateAccountResponse> restResponse = Response.success(bodyResponse);
Account account = Account.builder().build();
when(translator.convertEntityToRequest(any())).thenReturn(request);
when(restClient.createAccount(any())).thenReturn(restCall);
when(restCall.execute()).thenReturn(restResponse );
when(translator.convertResponseToEntity(any())).thenReturn(account);
//Execution
Account result = adapter.createAccount(request);
//Assertions
assertEquals(result, account);
}
}
Oh God! 🤦 ¡Kill me now!
You don't need to do everything in a spring manner, creating every class as a @Bean or a @Component. You are a Java developer, and there are better ways to do your everyday job.
Use the language constructs, there are static methods, static classes that can do the job; for example, one possible refactor may be the following:
@AllArgsConstructor
public class AccountAdapter implements AccountPort {
private final RestClient restClient;
public Account createAccount(final CreateAccountEntity createAccount) {
CreateAccountRequest request = AccountAdapterTranslator.convertEntityToRequest(createAccount);
Response<CreateAccountResponse> response = restClient.createAccount(request).execute();
return AccountAdapterTranslator.convertResponseToEntity(response);
}
}
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class AccountAdapterTranslator {
public static CreateAccountRequest convertEntityToRequest(final CreateAccountEntity createAccount){
return CreateAccountRequest.builder()
.field1(createAccount.getField1())
.field2(createAccount.getField2())
...
.build();
}
public static Account convertResponseToEntity(final Response<CreateAccountResponse> response){
return Account.builder()
.fieldX(response.getFieldX())
.fieldY(response.getFieldY())
...
.build();
}
}
AccountAdapterTranslator is not a @Bean or a @Component, and it doesn't need to be. Just a class with static methods is enough, and you don't need to create a mock for testing purposes; just let their methods complete the execution.
Some of those things happen because sometimes developers tend to do things automatically. Thinking twice is an excellent strategy to do the best that you can do when developing a feature.
There are always better ways to do the job. It would help if you didn't forget that before being a Spring developer, you are a Java developer, and before that, you are a Developer, and your job is to solve problems.
What do you think about the Spring illness? What other things do you think the Spring illness does to Java Developers?
Top comments (4)
Are you really advocating for static methods? If yes, as you wrote:
If no, then your writing is pretty confused (or confusing).
Here's an old post of mine: The case for Spring inner beans. Nowadays, you can replace the XML with a configuration class. This way, you can keep a a testable design at every step, and register only the beans you want in the factory.
Hello @nfrankel , thanks for your comment. I have been following you long time ago.
Yes, I advocate for static methods, that's the point of my article.
The expression:
"Oh God! 🤦 ¡Kill me now!" is because of the ugly test and design of using every class as a @Component
Thanks! But then, you know I cannot agree with you on
static
methods.The cost of creating an instance method instead of a static method is marginal, and the benefit is that you can test its callers in isolation. If you feel it doesn't deserve such test, fine. But you might change your mind later, the software will evolve so you'll need to test it later, etc.
The only thing I agree with is to stop making every class a Spring bean.
Finally, avoid annotations when you can and don't use autowiring a.k.a. magic.
Well, Nicolas, that's ok. I think it depends. If the static method has a bit of complexity that's better to do an Instance method to test it isolated, but if it's just a bypass like a method in the example and that's my point (My bad 👎, I didn't make it clear) on that kind of methods that don't do a complex functionality it's better to do just a static method.
Thanks for your comments.