Iteration 8 - billing module extraction and differentiating users

Welcome to the 8th iteration of WebCalc. Previously I promised to extract billing module from the app module and that’s what I’ll start with. To IDEs then.

There’s nothing really fancy about extracting this module. You can see the full commit 9f191153, but it really boils down to creating a new module and copying build.gradle from calculator module, adding it to settings.gradle, moving Billing and BillingShould to the new module and importing new module in the app module.

I’ll also do a few upgrades while I’m more on the technical side of things and not dealing with business code. Gradle does seem old already so I’m:

  1. installing it with SDKMAN
  2. and calling gradle wrapper --gradle-version=5.4.1 --distribution-type=all in project’s root directory.

Results are in commit 6cc51b73

Tests are still green, so I can move on.

Next thing to upgrade is Spring Boot, the newest version seems to be 2.1.4, so:

Tests keep being green, no matter what I throw at them. As there are no other things to upgrade I can go back to delivering something to my theoretical stakeholders. This time the idea is to take users into account when billing, so if user A executes some calculations, he’s billed, but user B is not, logical.

Now there’s probably no way for me to escape from having actual users, so I’ll use what I’ve configured Spring Security for back in iteration 6. As usual, I start with a test and I need to break two APIs, Controller’s and Billing’s by adding a user in there. This will force me to probably rewrite half of the application and I’m just wondering if that could have been avoided.

I’m not exactly introducing user object, I’ll see if just having IDs will be enough, I hope it will. As promised, this test breaks API horribly, so I need to fix both Calculator and Billing classes.

This, in turn, forces me to change both controllers and the rest of the tests. In controllers, I’ll do a nasty thing and just pass in a NULL. For now, this will make the code compile and if some test will fail later on, I’ll fix it.

In the tests, I need to create some user ids and just use them.

By the way, all those refactorings are quite easy to do with a good IDE. Now everything compiles, tests are executed and all green except my new one. I can finally start working on making it green.

The second assertion in my test is failing, apparently user B was also billed for what user A did. No wonder, there’s currently only a global balance, I need to change it to some map from user id to a balance. That also means, that CalculatorObserver would need to carry user information. Not an issue:

One last thing, Calculator needs to pass userId to the observer call.

I’m done, all tests are now passing, phew. The mere amount of time I had to spend in red (having not passing tests) shows how still inexperienced I am when it comes to TDD. But we all need to learn constantly, maybe next time I would be able to avoid such a situation. Or maybe not because there’s no other way. I’ll see if I can get something out of this.

What bothers me now is that internally everything works, but I have those two NULLs in controllers. To actually fix those I need a very similar test, but working from outside, through the REST interface. And I’m wondering right now why I do not have such test already. It seems I did the same mistake again, I started adding a feature not from outside, I assumed I know how to implement it properly. Anyway, the test:

I also changed the constants for username and password, because now I need two of them. When I run it, already the first assertion fails, apparently I didn’t even charge the correct user. Yes, now I need to fix the NULLs in both controllers.

It seems that I need a custom UserDetailsService and a custom User class. The reason is that I assumed that a user has an id and it is of UUID type. None of that is true with default Spring Boot configuration. This is getting bigger than I was hoping, but the only other choice I have is to revert those choices and I don’t want to do that. It seems that I’ll start growing some Users module, but as usual, I’ll start adding code to the app module and will be extracting it later. Let’s start with User class.

Because I’m using Spring Security, I’m forced to make my User class implement UserDetails interface or extend directly from org.springframework.security.core.userdetails.User. This is creating a tight coupling between my application and Spring Security which I don’t like. But when I create my own User class, even if it’s implementing Spring’s interfaces, I might try to limit the damage. I’ll use composition instead of inheritance, as all good practices suggest, and just delegate what’s necessary. I could, if need be, remove Spring Security and reimplement its API, but changes to the implementation might not be that big. By the way, I’m starting to get the idea of why working with Spring Security is not such an easy thing.

OK, User is not enough, I also need my own UserDetailsService:

It contains only one user with the same username and password as previously. I also wired it up, so I should be in exactly the same place as before, let’s run the tests to see if I’m not lying.

I’m a liar, the error I’m getting now is There is no PasswordEncoder mapped for the id "null". That’s unexpected. A quick check on the Internet and I know I need to specify the password as {noop}9786f3gb4508c2393q7y, see here. The fix is simple:

Now I also don’t need default user configured in application.yaml file.

OK, now I’m in a position I wanted to be, all tests are green apart from my newly created one. But it is actually wrong, I should be already charging someone for doing any calculations, my new tests is only verifying that I’m not charging the wrong users. That’s how I end up missing important business logic by not writing proper tests. I wrote the tests for my Calculator class but didn’t do that for my REST API part. And I thought I’m doing TDD…

I’m adding a new REST API test, even before the currently red one, just to verify someone was charged. It’s actually a partial copy of this new test:

The fact that it is a partial copy suggests to me that there’s some duplication in the tests, maybe they’re not testing the right things, I’ll look into it is a minute. For now, I’m focusing on the test I just wrote. Luckily, it’s red so I need to fix it.

After inspecting the code a bit, to my surprise, I find out that on the application or Spring level I’m not injecting Billing as an observer to Calculator. This is getting more and more embarrassing for me… Luckily, the fix is quite simple:

Now things get more interesting because all my tests for billing going through the REST interface became red. The reason is simple and terrifying - the state from one test is carried over to the next test. Of course, the Map I have in Billing is not cleaned up between the tests so it carries over current balance. This is actually a good thing, in the real world I want that! In the tests though that’s a very bad thing. As I’m using Spring, there’s an annotation created for such situations, @DirtiesContext. As each of my tests assumes a clean state, I’ll use BEFORE_EACH_TEST_METHOD mode, like so:

I was expecting only one of my billing tests to turn green, but two of them stopped failing. The reason is, I can use a NULL as a key to a Map in Billing that contains balance of each user. As I’m passing in a NULL as a userId, my current implementation works. Not expected, but also not bad either.

OK, I finally managed to get the whole application to a state where it should have been before I started this iteration, better late than never. I can finally try to do something with my last failing test. It’s red because I’m not differentiating users in any way beyond controllers, userId is always NULL. I need to get the user in both controllers, in Spring Security world it is done like so:

Interestingly enough that doesn’t fix my test, but after taking a closer look I noticed that I simply lack a second user. The fix is easy:

Finally! All tests green!

I must admit that this iteration was a very challenging and frustrating one. And it was only my fault. Most of the problems were coming from my previous mistakes, the iteration was far bigger than it should have been, I had to add a test and complete missing implementation, and all that in the middle of working on something else. I was thinking several times about rewriting the iteration completely, fixing things first and then moving onto the actual thing I wanted to do. But again, this whole series is about learning. This iteration clearly shows how much trouble I got myself into just by forgetting testing things from outside and, in the end, missing important pieces of implementation. Plus, by actually going through all the pain, maybe I’ll remember it better.

Anyway, thanks for bearing with me. In the next iteration… I have no clue what I’ll be doing. Let it be a surprise for all of us. See you next time!