Iteration 2 - actual calculations and modules separation

Welcome to the second iteration of WebCalc implementation. Last time I managed only to get a basic construct working and only in a very crude way. Always returning “3” from a controller is hardly a proper solution. This time I’ll separate the app module from calculation one and then I’ll start digging into actual calculations.

But first, looking at Gradle files I’ve noticed a few unnecessary things. One thing is, that I’m specifying explicit versions for JUnit and REST-Assured, which, having Spring Boot, is not necessary. Another is, that I’m importing snapshots repositories I don’t need. Yet another, I’m using “compile” instead of “implementation” when specifying dependencies, where the former is not exactly deprecated, but the latter is preferred. And, at last, just by copy-pasting last time, I have a mix of single and double quotes, so I’m changing all to double ones. See commit 60acf63. (May I express here my dislike to how many different syntaxes are allowed to do one thing? I didn’t even start properly and already have a mess).

Having that aside I can focus again on the project. What I want to do is to move calculations from controller to calculator. That one doesn’t exist, so I’ll create it, starting of course with a test. Crazy enough the API will be very simple, take a string in, return a string, so all this parsing will be done on calculator side, not in the controller. Makes sense, a controller should only be responsible for handling the web part, so HTTP, declaring content type, etc. Parsing input belongs to the calculator. So, the test:

To make it green I need an implementation. It cannot be any simpler than:

Now I need to call the calculator from the controller, again, simple:

A bit crude, but the tests work. Talking about tests, it seems, that WebCalcApplicationTests, which was generated automatically by Spring Initializr, is obsolete. It makes sure, that ApplicationContext is able to start, but my RestApiTest is also verifying that I need a fully working application there. It also takes some time to execute which I just noticed. I can delete WebCalcApplicationTests.

The controller needs to be cleaned up. First, I create a calculator field and initialise it in the constructor:

Everything still works, but it’s still not perfect, I don’t want my controller to know how to create a calculator. I’m going to do it Spring way, create it as a bean somewhere in Spring configuration and use constructor injection, like so:

OK, I have now three classes, that belong to different modules, business logic, web and app. Time for some Gradle subprojects.

The first step is quite big in terms of commit size but provides little more than setting up the stage. It is required though. I’m creating a subproject in Gradle called “app” and I’m moving everything in there. This module, in future, will hold only classes responsible for setting up everything, starting Spring, instantiating context, etc. See commit 03e5db0.

Now things get more complex, as I want to extract a “calculator” module. That one will only have classes responsible for calculations and will be free of any framework dependencies, like Spring. It will also be compiled first. While doing that I faced some serious problems with, most likely, dependency management Gradle plugin from Spring Boot. Whenever I was trying to use it in calculator module, my tests in the app module were not seeing classes from the calculator. Playing around with Java, Gradle and Spring Boot versions didn’t seem to make my situation better. But, as you might have noticed, I was “inviting” Spring Boot project into my calculation module. I’m not going to pull in Spring itself, I just wanted to get the same version of JUnit and AssertJ everywhere, which this plugin could ensure. It didn’t work (or I’m too stupid to get it working correctly), so I had to go back to manually setting versions. Long story short: see commit 4601920.

Enough with Gradle, right now the structure is better. I could extract also the web module, but I don’t need it just yet, I only wanted to have calculator separated, because that’s where I’d focus next. First thing I want to do is to extend my test to be parametrised, so that I can easily add more test cases, like so:

Now I’m adding a second test case, that would force me to stop blindly returning “3” from the calculator:

Fixing this is quite easy, although I need to deal with parsing strings. At some later point in time, I’ll probably separate parsing from calculations, but I don’t know yet in which direction the code will go, so I’m not doing it now.

As you can see, I’m using BigDecimals, although I don’t need them just yet. It’s a conscious decision, I don’t want to rewrite all of the code once I reach a point, where I’d need some floating point operations.

I’m also adding a few test cases, just to be sure, that my adding also works for bigger numbers, as well as negative ones.

I used “obvious implementation” in the code, so no surprises here, all work.

I’m going to extract the calculator as a field in the test, it will help me soon.

Now I can add a new operation to the calculator: subtraction. As usual, I’m starting with a test:

Implementation is quite obvious if not a bit dirty:

Again, I’m adding a few more test cases to also test negative and longer numbers.

So far so good. Am I sure? Not really! I was so into calculator module itself, that I didn’t notice my integration test stopped working. But that’s easy to fix:

And that would be all this time. I definitely have a messy implementation of calculator itself, that I’d need to deal with soon. After I add a few more operations, and maybe partial support for floating point operations in the next iteration, maybe it will be more clear how to clean it up. Also, the tests for addition and subtraction look worryingly similar, for multiplication and division they would also look the same. But here, although it’s an obvious duplication I don’t want to merge them because they’re theoretically testing different operations. Or maybe I should? I’ll see next time. See you later!