Skip to main content

Reflections after writing simple Spring Boot library

· 4 min read

Sometimes learning from adversity is better than trying to avoid it. Taking it into careful consideration provides valuable lessons that will support you in the future.

I appreciate my job for one particular thing. That is, it provides steady steam of difficult problems that challenge my intellect. Recently I tried to wrap my head around problem how to write tests for semi-large Spring Boot codebase and refactor it (with no tests whatsoever).

I started from the assumption that when you don't have any legacy tests at hand first you write them. How can you know you don't break functionality without running the tests? But the code was very unfriendly and writing them would require writing mocks.

So I thought - why not automate stuff a little bit:

  • instrument given beans with reflection
  • dump args and results to json
  • load json directly in tests instead of writing mocks in plain Java

The problem

Main problem is that there are absolutely no tests. All testing is done by business. What a terrible waste of time... Each time something changes they click their way through all over again.

So I thought I will take snapshot of current state of things and secure some tests in the backend. But there is some many API methods! And each has nested call to service and service calls repository.

I would write aspects no problem, but repositories are as you recall interfaces for which the classes are generated by Spring. And to add to that I want to move fast and writing the Advices takes time.

I have come up with instrumenting the selected beans, but those beans can autowire another instrumented beans. So some mechanism has to be devised to order the instrumentation to do it from the bottom-most to the top.

Approach

So I have both "special" interfaces of repositories and plain beans annotated as components. Then you have to cater for both of the cases. For the interfaces I generated JDK dynamic proxies. For classes (as it happens they don't implement any interface) I used ByteBuddy instrumentation library to subclass them and load them with default classloader.

And I have instrumented beans. Now I unload the old beans and switch them to proxes. For that I used BeanFactory which I took from the ApplicationContext with some class casting. But that is not all beacuse you still have to autowire all affected beans.

First iteration of autowiring was very wasteful and boiled down to realoading everything. Then I came to the conclusion to build the dependency graph and updated only the nodes of the graph in reverse order. Spring BeanFactory has both methods for finding bean dependencies and dependants so it was pretty easy. One catch is that you have to actually autowire not the bean itself but the subclassed proxy. After that everything worked as charm.

If you are interested you can download the lib here.

Reflection

After writing the lib and being exposed to the project at work I have some thoughts.

First if you write Autoconfig lib to be used by other Spring components you absolutely can't poison the application context with unncessary stuff. If you you put bootstrap.yaml in your library it will float on forever and take predecence and collide with stuff already defined. Spring treats the same multiple bootstraps and in case of name collision the order is random. And if you specify it in config/- directory it will actually take predecence over anything else.

Moreover you should not define any components picked up by component scanning as these can clash with other things defined. Just confine everything inside the @AutoConfiguration annotated class with the exception of loading necessary stuff with @Import.

Likewise you should not include starters in your library because they will for sure accumulate for several libs. Just use specific non-umbrella deps even better include your own jars.

IF you have crosscutting concerns like authentication, you should not use internals outside the pointcuts. So for example you have authentication fetching the token before the feign call. Then using static class for caching the token and pulling it from the business logic is a big mistake.

Finally you should have mock database and the whole thing would not be needed. I would just happily write e2e tests then.

Conclusion

What will be of my little library is yet to be defined. However I learned about some best practices for writing spring boot libs and learned some lessons not to repeat over again. You I am happily sharing it to you by this article.