How Netflix uses Java
infoq.com
infoq.com
I just want to know where I can buy a bong ripping sloth [1] and whether they're legal in California.
Sounds like Apple Music.
Apple Music is the only app that has this problem.
Inconspicuously inspecting traffic to spy on people, even employees, is just bad.
I work at a company that once turned it on for GitHub.com.
All builds that pulled stuff from GitHub started to fail because all those Docker containers don't have the company's root CA trusted.
Also, that description made me lmao, thanks
To be clear: not ragging on OP in particular at all but more at the widespread practice at a company level.
My (cynical) take on the matter is that software engineers want to prove how smart they are to other software engineers, and they think using obscure jargon and abbreviations is a good way to do it.
It seems GraphQL was first announced publicly in February 2015.
https://netflixtechblog.com/migrating-netflix-to-graphql-saf...
Falcor was developed at the time Facebook was developing GraphQL in-house. It has similar concepts, but never took off the way GraphQL did.
Help me here, why do GC improvements cause CPU increase?
Seems like it's exactly that, OP cropped out the relevant bit where they list it having an overall performance benefit for that extra CPU time. Otherwise it could be assumed that it just hogs more CPU to get the same result.
A new GC could alleviate this by either going easier on the memory itself, or by doing allocations in a way that achieves better locality of reference.
I guess it depends on if they mean "we used 20% more CPU for the same output", or "we could utilize the CPUs 20% more".
For companies at much smaller scale than netflix where employee time is relatively more costly than computer time, this might even be the right decision. So you might end up with 20 servers at 50% usage, but using 10 servers will take twice as long but still appear to be at 50% usage.
If the bottlenecks and overhead are reduced such that it's able to make more full use of the CPU, you might be able to reduce to e.g. 15 machines at 75% CPU usage. Consequently the increased CPU usage represents more efficient use of resources.
>> which depending on the CPU metrics you use, may not show as CPU busy time
If your userspace process is waiting on memory (be that cache, or RAM) then you’ll show as CPU busy when you look in top or whatever - even though if you look under the covers such as via perf counters, you’ll see a lack of instructions executed.
The CPU is busy in this case and the OS won’t context switch to another task, your stalled process will be treated as running by the OS. At the hardware thread level then it will hopefully use the opportunity to run another thread thanks to hyper threading but at the OS level your process will show user space cpu bound. You’ll have to look at perf counters to see what’s actually happening.
>> you might end up with 20 servers at 50% usage, but using 10 servers will take twice as long but still appear to be at 50% usage.
Queue theory is fascinating, the latency change when dropping to half the servers may not be just a doubling. It depends on queue arrival rate and processing time but the results can be wild, like 10x worse.
If somebody knows how to make that insight actionable, let me know. No, hiring new people is not the answer. In all likelihood that swaps one hard problem for an even harder.
Sometimes, honestly, understanding the software its developing isn't an important business goal. It makes me personally angry, but most businesses do right by not picking business goals to placate me.
Sometimes you just have too many people.
Sometimes you can restructure your software and systems so that fewer people are working on a system and they can understand it better. Sometimes that would also involve restructuring your organization, which has pluses and minuses.
If you can ensure the smaller teams run similar stacks, there can be some good knowledge transfer when one team figures out an underlying truth about the platform that could apply elsewhere. And sometimes you get a platform expert team that can help with understanding and problem solving throughout the teams.
I always appreciate numbers and the differentiation between relative and absolute numbers in this case.
"We doubled our workforce in one week!" - CEO's first hire... ;)
The new ZGC uses more CPU but it provides some hard guarantees that it won't block for more than a certain amount of milliseconds. And it supports much larger heap sizes. More CPU sounds worse than it is because you wouldn't want to run your application servers anywhere near 100% CPU typically anyway. So, there is a bit of wiggle room. Also, if your garbage collector is struggling, it's probably because you are nearly running out of memory. So, more memory is the solution in that case.
On JDK 8 we are using G1 for our modern application stack, and we saw a reduction in CPU utilisation with the upgrade with few exceptions (saw what I believe is our first regression today: a busy wait in ForkJoinPool with parallel streams; fixed in 19 and later it seems).
G1 has seen the greatest improvement from 8 to 17 compared to its counterparts, and you also see reduced allocation rates due to compact strings (20-30%), so that reduces GC total time.
It's a virtuous cycle for the GRPC services doing the heavy lifting: reduced pauses means reduced tail latencies, fewer server cancellations and client hedging and retries. So improvements to application throughput reduce RPS, and further reduce required capacity over and above the CPU utilisation reduction due to efficiency improvements.
JDK 21 is a much more modest improvement upgrading from 17, perhaps 3%. Virtual threads are incredibly impressive work, and despite having an already highly asynchronous/non-blocking stack, expect to see many benefits. Generational ZGC is fantastic, but losing compressed oops (it requires 64-bit pointers) is about a 20% memory penalty. Haven't yet done a head to head with Genshen. We already have some JDK 21 in production, including a very large DGS service.
Yep. G1 in newer JDKs is very different from G1 in JDK 8, but Parallel GC has also seen very significant improvements: https://kstefanj.github.io/2021/11/24/gc-progress-8-17.html
Do you have an (un)informed opinion on minimum task sizes for the green threads?
My interest is re-factoring java code to reduce total wall clock time, on large compute with plenty of memory/cache.
In Java 8 (afaik) there were pretty much no generational or concurrent garbage collectors, so garbage collector would happen in a stop-the-world manner: all work gets put on a halt, garbage collection happens, then the work can resume.
If you have a better GC, you have shorter and less frequent needs to do a stop the world pause.
Hence the code can run on cpu for more time, getting you higher cpu usage.
Higher cpu usage is often actually good in situations like this: it means you're getting more work done with the same cpu/memory configuration.
G1 only does the marking concurrently, the evacuation is done in small pauses. A decade ago, there was only one concurrent evacuation GC available in Java, C4 from Azul. Now, we have Shenandoah and ZGC.
Anyone on in the inside know?
It varies from team to team; the "Studio" organization that supports creating Netflix content does lots of nodeJS due to the perception that it's faster to iterate on a UI and API together if they're both in the same language. On my team, we're very close to 50/50 due to managing a bunch of backend, business process type systems (Java), and a very complex UI (with a NodeJS backing service to provide a graphql query layer). Regardless, the tooling is really quite good, so interacting with a Node service is roughly identical to interacting with a Java service is roughly identical to interacting with anything else. We lean into code generation for clients pretty heavily, so graphQL is a good fit, but gRPC and Swagger are still used pretty frequently.
Things may have changed in the last 5 years, though.
GraphQL is interesting to me, I thought the clients were pretty similar across all platforms, meaning their API usage should also be similar enough to not need the flexible nature of GraphQL. But then, it allows for a lot more flexibility and decoupling - if a client needs an extra field, the API contract does not need to be updated, and not all clients need to be updated at once. Not all clients will be updated either, they will need to support 5-10+ year old clients that haven't updated yet for whichever reason.
It was exciting when J2EE was dominating.
Once you learn the annotation based configuration it also saves a lot of time.
The performance is valid but it will only keep improving.
https://github.com/spring-projects/spring-boot/issues/33044
Take any similar issue and you'll see a bunch of people who try to find a solution for them because they just aren't repeatable at all. The underlying issue is the auto configuration doing things you can't follow quite properly. It's like it wasn't mean to be understood. Issues like the one I linked above also show me that the spring dev crowd also doesn't understand the ecosystem anymore. The problem is complexity and automagic.
/e: this is what I mean in a nutshell https://news.ycombinator.com/item?id=37839306 and that will happen regardless of how much you "learnt" about spring boot ...
Honestly I have gotten so use to reading the decompiled source of code in IntelliJ to determine how to enable / disable items it is second nature.
Given the expressiveness and power behind the system that is a trade off I am willing to take. To a certain extent I think it is a trade off of using a tool that has so many permutations.
What you are saying is certainly justified.
If you are referring to the dependency injection container making use of reflection, then Spring Native graduated from experimental add-on to part of the core framework some years ago. You can now opt for Quarkus/Micronaut-style static build-time dependency injection, and even AOT compilation to Go-style native executables, if you're willing to trade off the flexibility that comes with avoiding reflection. For example, not being able to use any of the "@ConditionalOnXXX" annotations to make your DI more dynamic.
(Personally, I don't believe that those trade-offs are worth it in most cases. And I believe that all the Spring magic in the universe doesn't amount to 10% of what Python brings to the table in a minimal Django/Flask/FastAPI microservice. But the option is there if your use case truly calls for it.)
Honestly, I've never run into anyone who considers Spring to be "the bane of their existence", where the real issue wasn't simply that the bulk of their experience was in something else. Where they weren't thrown into someone else's project, and resent working with decisions made by other people, but don't want to either dig in and learn the tech or else search for a new job where they get to make the choices on a greenfield project.
Debugging why something doesn't work in Spring can also be a nightmare.
And the reference point for "compile-time safe framework" is of course not Django, which is written in a dynamically typed language...
Every time I've seen issues where the order of test execution matters, it's either been: (1) someone one writing integration tests and calling them unit tests, or (2) transaction management issues with H2 or some other embedded test database.
For #1, you're going to have a bad time with any stack. For #2, transaction management with Spring Data JPA is admittedly a tricky subject to learn. However, most of the time you can simply slap a "@Transactional" annotation on unit test methods that mutate the database, and that will cure what ails you.
> "Debugging why something doesn't work in Spring can also be a nightmare."
I don't know what could make Spring more challenging to troubleshoot than any other Java framework, other than diagnosing issues with complex dependency injection config. And that is often grossly overblown. Most of the time, you don't HAVE to use reflection-heavy tools like "@Profile" or "@ConditionalOnXXX". Keep it simple and there's really not much magic there. And IntelliJ or any other professional-grade IDE can help to manage the magic you do choose to employ.
> "the reference point for "compile-time safe framework" is of course not Django"
I'm simply saying that in any "X Stack Considered Harmful" discussion, eventually you have to put your cards on the table and disclose which stack you are comparing X to.
Everything is written in Rust here in the imaginary fantasy world of HN. But back in the land of the real, almost all line-of-business server side API development is written in either Java/JVM, Python, .NET, Node, or rarely some PHP or Ruby holdovers. Roughly in that order. Occasional oddballs here or there using Go or something else, but not common enough to make a dent and typically very difficult to evangelise.
So with that palette available to me, I'm going paint server-side microservices with Java and Spring virtually every time. My default comparison is to Python simply because it seems the greatest rival today in terms of adoption. But if we're looking at strongly-typed compiled languages only, in the business world in practice that limits you to Java/JVM or C#/CLR. Which is like saying that Coke is the bane of your existence and your company should be drinking Pepsi.
Yeah, h2 is a very very bad idea. Hibernate and jpa are also garbage (proxies, lazy loading and transactions are just a big footgun). Have fun trying to use Sprint security and proxied hibernate objects. Somehow you have no guarantee that the class you receive and are checking and is guaranteed by code is what you expect it to be because of proxies. Absolute nightmare. We're using testcontainers, which has their issues on its own.
> I don't know what could make Spring more challenging to troubleshoot than any other Java framework > "@Profile" or "@ConditionalOnXXX"
We have one configuration with @Profile to prevent it from configuring stuff in tests. Our biggest issue is that code that compiles is not guaranteed to run or even crash when running (@Lazy anyone?). I hate this. It caused us so much pain. A bigger issue really is the automagic I was quoting. They just configure shit together because they think it's nice. Also causes a lot of headache. You have to enable/disable a random bunch of shit until it somehow works. And after they released a version fixing vulns that were open for > 1 year because of old dependencies this vodoo breaks. (Especially the garbage that is spring security. Still no migration guide for ACl in v6 huh?)
Fun fact: not a fan of Rust. It's totally overblown for web backends. I love me some Go, but having the ability to use gradle multi-modules in a ports and adpaters architecture is just plain awesome. Not sure where the journey will take us, but Kotlin (which we use exclusively) is a lot of fun. I'd love to have native Kotlin at one point exclusively. No more failing builds because of a wrong jdk or anything.
Just everything about this ecosystem is fragile af. Not sure how we got to a point where shipping a simple Go bin is easier than shipping java. Don't get me started on the resource usage and performance.
I’m sorry to disappoint you, but Kotlin compiler is written in mix of Kotlin and Java which is being called from Gradle.
> Just everything about this ecosystem is fragile af. Not sure how we got to a point where shipping a simple Go bin is easier than shipping java.
./gradlew distZip and you’re done.
> Don't get me started on the resource usage and performance.
What about it?
Well, I've seen other cases - mostly regarded to mock pollution. And you may say that those are poorly written tests, and maybe you're right, but the problem - again - is that there are too many footguns. Even if you don't make mistakes, your coworkers probably will.
> I don't know what could make Spring more challenging to troubleshoot than any other Java framework
Here are some examples:
- Understanding what config and which beans are applied and why (especially in tests)
- Understanding and debugging @Transactional
- Understanding and debugging Spring Security
- Debugging why a request doesn't hit the controller method you think it should (this mostly relates to the former)
> I'm simply saying that in any "X Stack Considered Harmful" discussion, eventually you have to put your cards on the table and disclose which stack you are comparing X to.
Any framework that doesn't rely on reflection and prefers explicit config over implicit magic (that doesn't mean that there can't be any default values or behaviour). For the JVM stack, that could be something like Ktor, but there's others (even in pure Java). Even in dynamically typed languages, you have options like Sinatra and Flask. They can't give you type-safety, but at least they're more easily debuggable. Even Spring itself has tried to provide an alternative with Spring Fu, but unfortunately, there seems to be little momentum and it's still experimental: https://github.com/spring-projects-experimental/spring-fu
> Debugging why something doesn't work in Spring can also be a nightmare.
Making these points you can see if somebody actually used Spring in production ;)
How much time we've lost with caching in tests and other shennanigans. I've never had a stack causing more problems by just being how it is than with Spring Boot. Don't even get me started on the absolute clusterfuck of hibernate. I'd love to move on from anything from this stack and we're working on it! Absolute dumpster fire.
/edit: it's not meant to replace spring, I know what these things do. It's to remediate some of the performance issues and compile time safety, which using graalvm inevitably brings ...
You could look at Quarkus, Helidon, Micronaut or KTor if you want a Kotlin framework.
Easy: as I said down below, you can actually get wildly different classes because of things like Hibernate proxies. Also, you guessed it, the dependency injection part. I just hate it. Nothing works together. We have so many hacks and weird work-arounds because something doesn't work. (Websockets not working with tomcat for example, or the many funky troubles with using two modes of authentication at once).
> Honestly, I've never run into anyone who considers Spring to be "the bane of their existence", where the real issue wasn't simply that the bulk of their experience was in something else. Where they weren't thrown into someone else's project, and resent working with decisions made by other people, but don't want to either dig in and learn the tech or else search for a new job where they get to make the choices on a greenfield project.
In my previous startup, we used python and flask. Something I don't deem scalable for bigger teams/apps. We love Kotlin and Gradle (especially multi-modules). But there are so many drawbacks that just suck time. I have a bunch of private projects, all in Go. Fast and efficient as heck. Nothing I'd like to scale beyond maybe 5 people or 20k loc tho (no idea if bazel or something could help with that, no experience). You get a lot of good stuff, but you gain in fragility with Java/Kotlin.
Another point that just comes to mind is: how unsecure is this thing even? Dependencies are ages old, requiring you to litter your gradle build files with work arounds and overwrites so you're secure from some (often critica) vulns.
2. I don't care which programming language or framework you are using. If you hate dependency injection as a general pattern, then every alternative I've ever seen boils down to either: (1) monkeypatching all over the place to achieve the same goals, or else (2) just static hardcoding everything and not writing unit tests with any mocks. I mean, plenty of people utilize one of those approaches. They just usually don't do so while discussing safety and security with a straight face.
2. You cite Python and Go as alternatives, yet immediately acknowledge that they're unsuitable beyond small teams or apps (my God, I'll take dependency issues with Maven Central over PyPI any day of the week!). Honestly, this whole sub-thread seems to boil down to you just preferring to work on small codebases over large codebases. And that's perfectly fine! I just don't think that's language or framework-dependent.
Your only argument is that if this stack makes problems, you just haven't read enough and must be an idiot winging it. The truth is that this whole thing, especially the standard spring stack including hibernate, is just one big footgun. You can't isolate any of these, as this stack intertwines to achieve this level of footgun concentration. Some faults are hibernate, some spring's, some is due to DI, and some are due to java. And I'm fed up with the level of trickery we need to have to work with this without issues. I'd like to focus on the bugs and shitty architecture I introduce instead of others.
> (1) monkeypatching all over the place to achieve the same goals, or else (2) just static hardcoding everything and not writing unit tests with any mocks. I mean, plenty of people utilize one of those approaches. They just usually don't do so while discussing safety and security with a straight face.
These are both stupid things to do in general. If you do proper hexagonal architecture you need neither of these hacks.
Very well. You seemed to saying that Spring was far less attractive than some unnamed alternative, and I was trying to say that I don't see any such alternative in the same space. If you're basically saying, "Yeah, that's probably true, but I want to vent a little anyway", then fair enough.
I will however point out that Spring and Hibernate, etc are NOT tightly-coupled. You absolutely do not have to use any particular database persistence library with Spring, or any particular message bus framework or anything else. If you want to use jOOQ or MyBatis or raw JDBC or anything else, go nuts. The fact that most people gravitate toward certain de facto defaults doesn't make those requirements whatsoever.
> "If you do proper hexagonal architecture you need neither of these hacks."
I remember seeing that buzzword 15 or so years ago. From a glance at Wikipedia, this seems to be from the "Agile Manifesto" guy. It looks like most or all of this has been absorbed into 12-factor or microservices architecture or what have you. Regardless, I'd be surprised if it truly obviates the need for unit tests.
You got me!
> I will however point out that Spring and Hibernate, etc are NOT tightly-coupled. You absolutely do not have to use any particular database persistence library with Spring, or any particular message bus framework or anything else. If you want to use jOOQ or MyBatis or raw JDBC or anything else, go nuts. The fact that most people gravitate toward certain de facto defaults doesn't make those requirements whatsoever.
With tight coupling I actually meant tight integration and each bringing their own set of weirdness.
> Regardless, I'd be surprised if it truly obviates the need for unit tests.
Nah, absolutely not. But you get proper separation of concerns. You are almost doing it if you do spring. You just need a lot of interfaces and separate those concerns along specific lines. This gives you plain java domain code, isolated code for each type of side effects (be it web or files/database stuff) which you can test. You can also replace components very fast and easy without big refactorings.
My biggest pain with Spring/Hibernate/whatever is that it permeates every layer, even if you don't want it to. I brought up Hibernate because it took me ages, that Hibernate proxies everything and spring security doesn't understand that, doing funky things if you check proxied objects (don't get me started on string SpEL ... worst idea ever. Can't test shit and doesn't protect you from typos). And it just happened sometimes. That sometimes was when there was any lazy loading involved. Fun times!
With hexagonal (ports and adapters) you force yourself to separate the concerns. We also enforce it via gradle multi-modules and architecture tests. It works quite well so far. No damn compromises because hibernate forces you to have nullability in domain models where it wouldn't be possible from a domain point. It's all in different modules, hidden from everyone else. It also forces you to write domain code in plain java (or in our case kotlin). No funny side effects because of lazy loading, hibernate proxying, jackson funny business or anything else. It's been a bliss so far. We had to replace a lot of spring boot magic auto stuff (like anything hikari, hibernate, repos, models, DDLs, we need to do that manuall). But we can actually properly unit test code knowing that it can't fail a few layers up because of something missing. We can properly use Kotlin with all of its niftyness with sealed classes and so on. Really nice! Now we just need to solve the bean issues with additional tests and the typical gradle/java annoyances of throwing hurdles at you just because and then we have a very nice stack we love to use. And no footguns because we enforce a lot via compile time safety checks. We try to design everything so that it can't be used wrong. And best of all: a test suite without every test using something with transactions, a full application context or anything. Plain domain code, plain Kotlin tests. It's a big step up.
I can only recommend having a look into that. It's not that far away from a Spring Boot app with a lot of interfaces and Beans implementing that.
Hibernate looks easy but the abstractions have a cost associated with using and maintaining it. There are a lot of settings you need to get right. There were dedicated DBAs that would optimize it in the past. You could just use JPA to make the life simpler.
Tomcat.. I mean why? It was great but I'd say it maybe went out of pace compared to everything else. Why not embedded Jetty. At this point I'm starting to have doubts about how you deploy the services to begin with.
Dependency injection is actually great albeit the usual problem is that you need to read books to understand it as there is more than one way to do everything. My pain point was usually related to the differences you need to do among Java, Groovy, Kotlin but otherwise it is awesome.
Flask is shite, basic, Python has a hard time solving its mess with dependencies and its multithreading support is meh. Go is great I love it. But if you want to create enterprise software, Java ecosystem has you covered and the engineers are cheaper to hire.
> My pain point was usually related to the differences you need to do among Java, Groovy, Kotlin but otherwise it is awesome.
Yup, agreed! We got to run kotlin pretty nice and got to play it together nicely with gradle convention plugins.
Agreed on Python. Was my first properly learned language back in the days and my first backend lang. Absolutely annoying. I also love go, but it lacks something like gradle, especially multi-module support. I love it but don't see it on a scale as our Kotlin codebase.
> engineers are cheaper to hire
I think there's also the benefit of an exotic language. You might only get 10 CVs, but you could probably hire half of them because only passionate people bother to look into exotic things. With java you have a bigger bandwidth ... which causes a lot of work.
I don't fault Spring for it, and we were on Java 11 back then with just a little bit of new hotness. Java itself just didn't lend itself to the best ergonomics.
And you could fault Ruby or any dynamic language for the same, but they usually save you a little bit of overhead or boilerplate.
This never really sounds like a Spring or Java thing. It always sounds like a "not liking dependency-injection as a general pattern" thing. My issue with that is two-fold: (1) people should just say that, and not tie it specifically to any one stack, and (2) the alternatives seem to be either monkeypatching or else writing untestable code, and those alternatives are hardly any better.
Decorators and annotations are essentialy higher-order functions and higher-order classes, but there is nothing about them that makes it intuitive. They actually become quite difficult to write and much harder to debug, OOP's shit version of a monad.
But I do want to learn Spring. What material would you recommend?
If that's what they mean, I agree. I've seen dependency injection frameworks used in a bunch of different companies, and there are always people who consider it an essential lifesaver, like they just can't imagine working without it, and it always baffles me, because I've worked on equally large codebases that didn't use it, and it was occasionally a significant annoyance to pass dependencies by hand, but never equal to the annoyance of dealing with an automatic dependency injection framework.
This repeated experience of working with and without dependency injection, finding dependency injection to be at least as much hassle as it saves, and seeing that the people I've worked with who choose dependency injection have massively warped impressions of what it's like to work without it (they often think it's, like, impossible) has led me to see it as a tool that is driven mostly by FUD, at least at the scale of code that I have worked with.
And that's without even considering the deleterious effect that dependency injection has on design. In my cynical moments I think this is the real reason people love dependency injection. It's not that people hate the five minutes it takes to figure out how to manually pass a dependency to a module; it's that they hate the subconscious thinking that happens in those five minutes, as they see how the change affects the code, and it dawns on them that it's a code smell for every module to depend on everything else. Sometimes a dependency is a code smell, and dependency injection means you barely get a whiff, so faint you can pretend it's just your imagination. Doing it by hand means you get a few minutes to bask in the stench. You can't pretend you didn't notice.
Getting people to care about modularity, coupling, and cohesiveness in an application with dependency injection is markedly harder, just like it's harder in a language with global variables, just like it's harder to get people to think about APIs and modularity in a monolith than in microservices. And for me that's the worst part of working on codebases with pervasive dependency injection! Dependency injection is a massive liberating force for people who want to work without thinking about design. Instead of thinking about it, they just add another spaghetti dependency and keep on going, and do the same thing tomorrow, and the next day, and the next day. It's impossible to stop them! Adding a dependency is so immediate, so easy, there's no moment where they have to stop and think, "Hmmm, why am I using a dependency in module A that was only ever used in module B before? I'll need to instantiate it at the application level instead. But that means I need to pull some internal logic out of B so it can be run before B exists. Should I really be doing this? I'd better think/ask about this before I do it." Instead they just add an annotation and see if the dependency injection framework can figure it out. That's the only kind of problem I can think of that dependency injection excels at solving: problems that should never have been solved in the first place.
Your comments are confusing on whether you eschew a dependency injection framework, or eschew dependency injection as a pattern in general.
If it's the former, then you are certainly free to roll your own DI rather than leveraging a library such as Spring, Quarkus, Micronaut, Guice, Dagger, or any of the others out there. I strongly doubt that you'll implement something better than any of those. However, having written the code yourself you'll be more likely to understand it all, and that can be attractive for many people.
If it's the latter, then it's harder to take the position seriously. Look, the Java ecosystem certainly suffered from extreme OO over-engineering in the late-1990's and early-2000's, no doubt about it. A lot of people went too far overboard with enterprise-y design patterns, for sure. But that pendulum has been swinging back for over a decade now... and in the 2020's, the point of using DI is not to go crazy with the enterprise design patterns. The point is write code that can support an actual unit test suite!
You use DI so that your test suites can inject mocks or stubs, to isolate external integrations from the code under test. I don't care which language or framework you use, or whether you're using a 3rd-party DI library or implementing the approach manually yourself. If you're not using DI to accomplish this in your tests, then you're either: (1) using monkeypatching to achieve the same goal in a more dangerous manner, or (2) just writing un-testable code, and perhaps writing some integration tests that you pretend are unit tests.
My preferred approach to dependency injection is using the plain mechanisms of the language I'm using to pass dependencies as constructor and function parameters, without any framework.
I pass test fakes the same way. When doing things this way feels burdensome due to overly complex method signatures or overly complex initialization code, I look for ways to improve my design.
This works at every scale of code I've worked at. It's possible there's some size of project that it doesn't scale to, but for every project I've worked on where another engineer swore that dependency injection was absolutely necessary on the project, I've worked on a larger project where nobody ever suggested dependency injection and everything was fine.
My experience doesn't prove that dependency injection isn't necessary at some larger scale that I haven't experienced, of course, but it does convince me that the industry is rife with programmers who think they can't get by without it at scales where it is not only unnecessary but probably harmful.
Dagger library does that (generates all the glue code during pre-compile phase), so all the dependency-injection already happened before runtime. Also, JVM has an easier time reasoning/optimizing the code.
But I also found that writing the glue code by hand, same way as Dagger would do, is not that hard IMO.
Q: Why does my webapp run on most of our tomcats but not this one?
A: That tomcat server has the redis client libraries on the classpath, so Spring Boot automatically started up a connection pool connecting to localhost on the default port, and automatically includes that in the healthcheck endpoint. Since redis isn't actually running there, it fails to connect, so the healthcheck always fails and the app never shows as running, even though your code is working fine.
Q: How do I turn that off?
A: Upgrade to the next version of Spring boot and then add this undocumented annotation to your configuration
Q: How was I supposed to figure any of that out?
A: Hahaha fuck you
Spring proper is fine, useful even. Spring boot is an absolute nightmare of COME FROM style incomprehensibility.
PS: we replaced/will replace all microservices with Go.
http4k[0] is a lightweight but fully-featured HTTP toolkit written in pure Kotlin that enables the serving and consuming of HTTP services in a functional and consistent way.
http4k consists of a lightweight core library, http4k-core, providing a base HTTP implementation and Server/Client implementations based on the JDK classes. Further servers, clients, serverless, templating, websockets capabilities are then implemented in add-on modules. http4k apps can be simply mounted into a running Server, Serverless platform, or compiled to GraalVM and run as a super-lightweight binary.
Apart the from Kotlin StdLib, http4k-core module has ZERO dependencies and weighs in at ~1mb. Add-on modules only have dependencies required for specific implementation.
I agree it is not common to do it, most teams follow the autoconfiguration madness.
Like srsly even DropWizard is better than Spring lol, let alone other even simpler frameworks like Ktor which is built on a much improved language over Java
1. It's always best to start people off with plain old spring, even with an XML context, such that they understand the concepts at play with higher level abstractions like Boot. Hell, I even start with a servlet and singletons to elucidate the shortcomings of rolling your own. 2. Don't fall prey to hype around new projects in the Spring ecosystem, such as their OAuth2 implementation, since they often become abandonware. It's always best to take a wait and see approach 3. Spring Security is/was terrible to read, understand, and extend ;)
I did not like much of the XML, because it always seemed lot of duplication. All you doing is copying bean definitions and changing bean id and class/interface most of the time. But it became non issue over time. Now spring boot made it really easy with all those annotations.
For me delivering business value is the most important metric when I am comparing frameworks. Spring Boot wins every time.
Gradle, however, is a dumpster-fire of footguns and obtuse and non-debug-able DSLs.
For small projects, it works fine, as long as the complexity doesn't grow beyond a certain point for the small project (aka, doesn't grow to a big project), and is maintained by the same person.
For a large project, i do not like gradle at all. Maven is a much better build tool, since standardization is the best thing since sliced bread.
The article is superficial, mentions Java but seems that Groovy had a more important role there. But in the end, it really talks about JVM.
It reads like a PR piece from a Oracle and Netflix partnership to promote Java. Oracle have done that before.
I might be wrong