
Lesson Description
The "Testing for Modularity" Lesson is part of the full, Enterprise Java with Spring Boot course featured in this preview video. Here's what you'd learn in this lesson:
Josh introduces the @Externalized annotation, allowing messages to be published across multiple JVMs. The testing features of Spring Modulith are also highlighted, including the ability to print the modules and their dependencies. The Documenter is used to generate a UML diagram of these dependencies.
Transcript from the "Testing for Modularity" Lesson
[00:00:01]
>> Josh Long: At this point I'm entirely in the same jvm. What happens if I want to have another jvm? Well, you have this mechanism, you have these modules in spring modulus to support messaging and externalization. So in addition to publishing the messages from one component to another in the same jvm.
[00:00:18]
You can add externalized to this and you put in there the scheme for whatever messaging module you're using. So, so if you're using Kafka, you'd put a topic name, right? Topic name. Or if you're using RabbitMQ, it'd be the exchange name, right. I contributed my favorite module, which is the spring integration messaging module which can in turn talk to anything you want, right.
[00:00:40]
You saw how you create a channel and you create an integration flow. Well, here you put the channel name and this message will get published into a channel. And from there you can talk to anything that spring integration can talk to, including SQS. And Pulsar and Kafka and JMS and file systems and FTP and email and tcp, IP and whatever you want, right?
[00:00:57]
So I'm not going to set this up, but you should know this is available. You just make sure to add externalized and then set up the right dependency, right? So for example, dependency, spring modulate events, messaging, that's the one that does spring integration, for example. And there'll be one for events.
[00:01:17]
Is it sqs, I think and Kafka and you know, you get the idea. Okay, so now we've got externalization in the system and we've got our decoupled code and it's modular and all that. How do I know if it's modular? What does modular mean? How do I know if I am modular?
[00:01:34]
Right well, let's write a test. As with all things in life, you just, you write a test. So var am application modules of and we'll say adoption applications.class. I'm going to say am Verify. Is my code modular? Let's just see what that means. So this is my test.
[00:01:56]
It's junit. I've got a test method here and I'm saying, is it modular? Looks like it's modular. It's green. Okay, great. What does that mean? What modules do I have? Well, let's ask. Am I just going to print out the modules? I'll say system out, print line, am.
[00:02:11]
Okay, go down here. Looks like I've got two modules. One's called adoptions, one's called vet. These are the logical beans inside of each module. Here are the dependencies. One package is Injecting types from the other one. If my spring module knows about the structure of my modules, then maybe I can articulate that for the, for the architects in the crowd.
[00:02:37]
Right, great. So let's write it out. Go over here. And that generates a spring module doc folder. And I go here, sorry. And it gives me this nice architecture diagram. This is a C4 component diagram. This is a way to model components in system boundaries. Not like uml, but like system boundaries.
[00:03:00]
And this is using platuml, which is a text format. So you can check it into version control and then use something like graphviz or plantuml, whatever to generate this image. Okay, so this is a nice image that will evolve with the code base. This is much better than the status quo.
[00:03:16]
How many of you have logged into some wiki page to find a diagram, checked into that wiki from some well intentioned architect from like the Mosaic age, Right? Like, it's just a totally different thing. This is, this is actually up to date with the code and with the state of the code.
[00:03:32]
So, good. So we've got modular code, right? We got. We're able to see what's happening, we're able to observe it, we're able to write our code in such a way that it's decoupled and we're able to test it. This is spring modulus. It's support for writing code that is modular and I say modular and I say that you can test it.
[00:03:48]
What does it look for? Well, behind the scenes, spring modulus is packing up or packaging rather up something called arch unit or arc unit or whatever. You know what my favorite word in French is, by the way? Architecture. Come on, don't tell me that doesn't sound great. That's a great word.
[00:04:08]
Anyway, arch unit is a. It's a way of testing architectural constraints in your system. And we're using that behind the scenes. And there are several things we're looking for. First of all, if you created a like, this is a module, Adoptions and vet are both modules for our purposes here.
[00:04:25]
And if I created a new module and I created a new package within that root package, that root package is the module. If I create a new package in here called validation, and then I create a new class in there which is meant to support validation, right. Well, this has to be public.
[00:04:39]
Naturally, it has to be public. I have to make it public in order to use it from my service. So in this case it's a limitation of the language proper, not my design that this type is Public, but I have to use it. It's clearly meant to be an implementation detail for my dog adoption service.
[00:04:58]
Let's restart this test and see if I've broken anything. Nope. So far so good. Now let's go back to the vet and we're going to inject this root type validation into this module, right? Add that to the constructor. Here you go. I'm going to restart the test again.
[00:05:15]
So remember, this is a detail. It's a nested package in another adjacent root package and I'm injecting it into another module. I'm injecting it into another root package. Well, that broke the test, didn't it? It's saying, hey, you've inadvertently leaked implementation details about one module to another. And so if you go down here, it says module vet depends on a non exposed type called validation within module adoptions.
[00:05:41]
So basically, even though that's public. It's not actually meant for everybody in the code base to consume the nested type validation under the package Validation was clearly meant to be a concern of adoptions. Not the other package called vet. So if I have a root type in adoptions and it's adoptions foo and I inject it into vet, that works.
[00:06:00]
We saw that earlier. I injected Dr into the dog adoption service across two root packages. But this nested package is. It's meant to be an implementation detail. So the tests bark at me. They say, hey, you're off the rails here, right? This is what we want. And speaking of which, this reminds me of my least favorite anti pattern in like for the ages, basically, which is the thing you get when you do Ruby on Rails, right?
[00:06:26]
So imagine you have app.models.customer, okay? And then in order for me to use that, I have to create a. What did I create? What is it? Repositories, right. And that's customer repository. And then I have to do app services. Customer service, right? And then in order to that I have to do controllers and then that becomes customercontroller.
[00:06:53]
In order for this to work, this has to be public. That has to be public. That has to be public. And maybe that does you. You probably will just for consistency. This is a terrible, terrible way to arrange your code. It's based on the role of the thing in the technical architecture, right?
[00:07:07]
This is a model and this is a repository and that's a service. It has nothing to do with the feature that you're implementing. A much more natural way to organize your code is to say app customers and then have your customer type customer controller etc, right? If you do it this way, nothing needs to be public.
[00:07:27]
You can be very deliberate about what is public. You can keep the concerns that are related to each other together and you can test this slice by itself. You can test just the functionality related to customers. And you know where the component boundary is, because it's the events or the public types that you've exported.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops