Smart Package Structure to Improve Testability
The pluses and minuses of packaging by layer, feature, etc. abound. Here's a look at testability, and smart package structure.
Join the DZone community and get the full member experience.Join For Free
There are many ways of dividing whole application into packages. Discussions about pros and cons of packaging by feature or by layer can we found on many programming blogs and forums. I want to discuss this topic starting from testability and see if it will drive to any meaningful result.
At the beginning let's try to describe what we usually want to test in our applications across different layers. Let's assume standard three tier architecture. At the bottom we have data layer.
Depending on our attitude to Domain-Driven-Design we'll try to maximize (for rich, business-oriented entities) or minimize (for anemic entities built only from getters and setters) test coverage. In the second approach it's even hard to say about any tests, unless you don't trust Java and want to verify if get can retrieve value assigned before by set invocation. For rich entities we definitely want to verify business logic correctness. But to be honest it almost always can be done by simple unit tests with proper mock setup. There are often thousands of tests in this layer, so we want them to be maximally fast. That's a great field for unit testing frameworks! Wait? Why don't you want to test entities with database? I can ask opposite question - why should I do? To verify if JPA or any other persistence API are still working? Of course there are always some really complex queries that should be verified with real database underneath. For those cases I'll use integration tests on repository level. Just database + repository + entities. But remember about single responsibility. Your integrational tests is checking only query - leave whole entity logic for unit tests.
Next layer is usually built from services. In DDD services are just working with repositories to load entities and delegate them whole business logic processing. As you can predict those tests will be pretty simple. Do you think we need database here? Will it provide any added value? Don't think so. And what about second scenario? Anemic entities in our model? Whole logic is concentrated in services so we have to accumulate our test coverage in this layer. But as we already discussed with domain logic it can we done without using external resources. One more time - all we need is a unit test. So still no database. We can run all tests based on repositories mocks. No problems with managing datasets leading to "expected 3 but found 2" tests failures. Just because some other test committed one more order with value between 200$ and 300$. Even if we want to use IoC framework here, it can simulate repository layer with mocks. Without proper decoupling from data layer framework would automatically load repositories via some scanning mechanism. And it's not something we want.
On top of the services we usually place something allowing users to use our application. It can we fronted, RESTful API, SOAP services, etc. What is important to check here? To be fair with our customers we should stick to the contract we have with them. This whole can be material for separate blog post, but narrowing down to REST services:
"If you will send we POST request to /users URL I'll answer with list of all users. Every user will have id as an integer and string with username."
OK - that looks as a contract. So what should we check in this layer? Of course if this contract is valid. Send HTTP request and verify if response contains array of users, from which every entry is build from integer ID and string username. Can we do it on top of the services mocks? Sure :)
So to encapsulate everything:
- data layer = unit tests for logic and integration tests with DB for complex query validation
- service layer = unit tests for logic and light integration tests without DB for testing IoC framework dependent logic
- front layer = integration tests without DB to verify customer contract
So far we've described in details what is worth to test on different levels. Now let's move to feature based packaging. It definitely helps to keep the code well organized when it's build around different business contexts. For large applications it's something that allows you to cut it down into many modules or even many applications. Without such feature layout such actions will require huge refactorings before. But is it still needed after splitting our monolith into applications? Just think about starting new application. What will be its base package? com.my.company.application? It's nothing else than a feature packaging :) But would you stop on this base package, or still will you split in into layers? As you see those two structures can live together.
For layer based structure our application will look like below:
com.company.application \.data \.config \.model \.repository \.service \.config \.api \.config \.controller
For feature based we'll get something like
com.company.application \.order \.client \.invoice
But usually as business logic always grows, it leads to splitting whole application into modules or services, so finally we get:
com.company.application.order \.data \.service \.api com.company.application.client \.data \.service \.api com.company.application.invoice \.data \.service \.api
To sum up. In my opinion packaging by layer is a must. It allows us to test each layer separately and keep our tests well organized. Package by feature is really useful in bigger projects. For microservices which are built around single bundled context more detailed division can lead to uncomfortable navigation. However code inside feature package should be still broken on layers for the same reason as mentioned above. Especially with Spring Framework layer-based structure helps us with setting useful component-scan, and won't drive us to setup a database just because we want to start context with two services. In my GitHub repository https://github.com/jkubrynski/spring-package-structure you can find sample project based on Spring.
Published at DZone with permission of Jakub Kubrynski, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.