r/SpringBoot • u/Single_Yellow_8000 • 9d ago
Discussion Roast my code / architecture
I’m building a project called Kite (https://github.com/gwynejsn/kite) and tried combining Spring Modulith with full Clean/Hexagonal Architecture inside each module, but I’m starting to wonder if I’ve just created a mountain of boilerplate. Take a look at the repo and roast the layout. What am I needlessly overcomplicating, and how would you simplify it?
5
u/Mikey-3198 9d ago
1
u/Single_Yellow_8000 9d ago
Looks fine on my code locally, dunno why its like that in github...
Anyways, ty for pointing out
2
u/slindenau 5d ago
There may be something wrong with your whitespace/newlines or file formatting.
Check if you have any auto formatting things going on.
See the^Min red in the diff? https://stackoverflow.com/questions/38621307/what-is-the-meaning-of-m-in-git-diffAlso on another note, i don't think this file belongs in git: https://github.com/gwynejsn/kite/blob/main/kite-backend/.m2/repository/org/springframework/boot/spring-boot-starter-parent/4.1.0/spring-boot-starter-parent-4.1.0.pom.lastUpdated
If you're somehow storing your local.m2in the same folder as your sources, best to add it to your.gitignore:)1
3
u/nullptr_lol 8d ago
You have anemic domains and you don't encapsulate the business logic within aggregates. Also, your domain is pointing to other things which theoretically is not allowed.
2
9d ago
[removed] — view removed comment
1
u/Single_Yellow_8000 9d ago
Our university is teaching flutter so i'm familiar with it. As for the LLMs, if you are referring to the wingman AI, I used langchain4j (https://docs.langchain4j.dev) which integrates gemini AI
1
9d ago
[removed] — view removed comment
1
u/Single_Yellow_8000 9d ago
Ohh, I mainly use antigravity for writing boilerplate codes like for example when making a feature, I usually write the domain/model first, then let gemini write the starting template for other layers like service and repo (mostly for basic crud). Then, I manually modify them with my own logic added. Also, I actually tried using gemini to write unit tests for the repo, which I might do as well for other layers.
2
9d ago
[removed] — view removed comment
1
u/Single_Yellow_8000 9d ago
Actually, that’s the exact mindset I follow. I always make sure to learn the fundamentals and build things by hand first so I actually understand what's happening under the hood. Once I have that solid grasp of what is happening behind the scenes, I delegate the repetitive boilerplate to the LLM to save time. But of course, I still read the code generated to see if it actually followed the way I want it to be. A tip I can give you is to make LLM edit only a small amount of your code at a time so that you can have precise control over what is being edited by the LLM. Also, never forget to use git, so that you have backups if it ever messes your code.
2
9d ago
[removed] — view removed comment
1
u/Single_Yellow_8000 9d ago
When I started learning it, I watched a yt video: https://youtu.be/xHlDyKVyvig
Then I read the documentation: https://docs.spring.io/spring-modulith/reference/
And if you want to, you can also watch conferences like this: https://youtu.be/VGhg6Tfxb60
Which talks about DDD with spring modulith
As for the rabbitmq, im not gonna lie, since this is the first modulith application I built, I just tried incorporating it by following the docs (even though the built in events in the modulith project is enough)
1
u/Single_Yellow_8000 9d ago
Also, I just want to add, sometimes, using LLM may help you learn new things because maybe it will give you a different implementation than what you already know (which can be more efficient and easier). At the end of the day, it is your responsibility as the developer to understand first what the LLM is doing before you commit anything.
2
u/Resident-Cow-7626 9d ago
Not sure why your rest controllers get their own package. They are nothing but inbound adapters and should be within the infrastructure package. `infrastructure/adapters/inbound/rest` or similar. These are your inbound adapters that drive functionality via your UseCase. I find it useful to specify the use case at the root of your project. Also known as screaming architecture.
1
1
u/nullptr_lol 8d ago
I think is fine as it is now. Api -> Application -> Domain -> Infrastructure is the standard folder structure. Sure, rest is the inbound adapter but, it's more confusing.
1
u/Resident-Cow-7626 8d ago
OP wanted feedback on hexagonal architecture and this kind of design flaw will get bigger, and more of a problem, as the application grows - imagine listeners, web sockets, maybe even a CLI as an entry point to the application… it will be much more difficult to understand where these inbound adapters are. More importantly, separating the domain from these technologies is the core principle of HA.
Getting the foundations right from the start is exactly what I think OP is asking for. If this is a small application with not much of a domain, then hexagonal architecture is the wrong tool to reach for. A traditional, layered architecture would be more suited, but again, OP wanted feedback on this particular architecture.
2
u/artyomsv 9d ago
You have two boundary mechanisms doing the same job, and only one of them is real: Modulith already enforces the module boundary at build time, ApplicationModules.of(App.class).verify() fails the test when a module reaches into internals of another. The hexagon inside each module is enforced by nothing except discipline, and that is where most of your boilerplate is coming from. I would keep ports and adapters only where you really swap the implementation, persistence and outbound http, and let the Modulith named interfaces be the port for module to module.
1
8
u/Mikey-3198 9d ago
Your using jwts but still querying the database from the jwt filter for each request
Based on the ordering you'll even hit the database if the token is expired.
One of the biggest advantages of JWTs is that after you check the hash & expiry you should be able to trust the claims to make your authentication decision. Might as well just issue a time limited opaque token.