Spring vs. Java EE
gist.github.com
gist.github.com
1. https://www.gartner.com/doc/reprints?id=1-3N8E378&ct=161205&...
I'm just going to leave this one at the doors of the bosses, both at vendors (like Pivotal) and buyers (like MegaGloboCorpotronic Consolidated Inc).
Disclosure: I work for Pivotal, on a Cloud Foundry project.
This isn't going to make me popular on HN but: I don't think this is actually the case.
Back in the mid 2000s I built EJB services by defining simple interface and implementation classes. I consumed the service by by injecting the interface into my clients. I tested my consumers by mocking the interface.
Now I build REST services. I have to define and document a complex dance of verbs, nouns, and payloads. Client code requires stitching together text fragments into URLs and JSON. There's no typechecking across API boundaries and testing requires me to stub out crappy HTTP libraries.
Hmmmm.
There are good things to say about REST (like transparency and ubiquity), but it's not totally clear to me that the world is better off now than we were back in those Dark Days. Yeah, there was a lot of awful EJB code, but I'm finding no shortage of shitty REST microservice code floating about nowadays.
It was refactored into a J2EE application after 7 years, and is now being refactored into a group of microservices, which causes a number of the longer-lived devs in the team to suggest everything old is new again.
On the other hand, if you wanted to get of the rails and have clients in multiple stacks and clients who talked to different stacks, then it just all fell apart. Even multiple vendors in the J2EE space could be a problem.
Being able to do things simply hid a lot of complexity. It was hard to investigate that complexity when things went wrong.
It feels like chaos today, with myriad front-end frameworks and best practices changing every three months, but I would take this over mid-2000's Java tech because it just simply seems like less magic. I can break out Postman and just see what's going on.
I agree that there's no golden bullet, and that there's no end of crappy REST definitions. Furthermore, it's mostly not even REST, not in a HATEOAS kind of way.
But that's ok, I still feel more productive than I did 15 years ago, and I wouldn't want to go back.
And REST now with tooling like Swagger is catching up with what SOAP already provided out of the box.
Kids, there are reasons why those features where there in first place.
Typically this includes database access, ORM, message bus access etc.
It also does so in a very integrated way so you can easily have for example a transaction that spans database modification and sending to a message bus.
I like it. I found that embracing it allowed me to simplify lots of code.
I don't have much experience with Java EE, so can't comment. My experience with Spring is not very positive--it enables and encourages applications and systems to have complex dependency graphs, and so often I've seen this result in giant balls of mud that, despite Spring's claim that it enables decoupled and modularized design, are rat's nests of unexpected coupling and hard-to-predict behavior.
I and others have generally found that using Constructor injection greatly decouples a design and simplifies testing.
And the Spring team have been saying so for at least as long as I've paid attention to the question[0]:
> Constructor-based or setter-based DI?
> ...
> The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.
> ...
[0] https://docs.spring.io/spring/docs/current/spring-framework-...
Constructor injection in Spring is more test-friendly; in turn that tends to allow us the liberty of more aggressively modularising the design. With other injection types either more bookkeeping is required to cleanly set up tests; or alternatively, "unit" testing becomes more integration-esque.
It's a second-order effect felt in the many small decisions that lead one to the mudpit.
That's why I used the analogy of dark matter. It's not directly observed, but it has gravity.
If you get in the habit of using the Spring test support, where classes under test get injected, then all kinds of injection are equally friendly, and the tests don't give you feedback on the weight of the dependencies because Spring does the heavy lifting.
And, in my experience, where you do inject dependencies by hand, the design pressure is actually towards coarser-grained objects. Why? Because you need to have enough functionality to test; with fine-grained objects, you have to construct and assemble an object graph in the test, whereas with coarse-grained objects, you just new up one thing.
How do you handle the Spring context start-up time? In Spring projects I've worked on, across hundreds of unit tests it's non-trivial. But I don't make any claim to know if we were doing Spring right or not.
Or you can use mocks and Spring Boot's test annotations.
But with field/setter injection, that choice isn't as easy.
Running a single test is a bit of pain, but running the whole suite is not a lot slower than if it didn't use Spring.
These are my thoughts too. No reflection or @MoreAnnotations to inject dependencies. I have an aversion to setter/field based injection because it allows you to instantiate an object in an incorrect state.
Oh yes. I recognise that JavaBeans had its reasons for being, but then you get to JPA and it's time for all the boggling.
Annotations are fine for intra-project code contracts, but using them for external validation means that if you, say, deploy on a application server with an older version of Jersey, you're suddenly SOL when none of your validation logic is executed.
The main reason to favor constructor based injection is that it stops objects becoming 'live' in an inconsistent state. I've seen actual production issues with setter based injection that can only be explained by the object being available for use before all the setters have been called.
It is the failure of concepts so if the tool brings the right concepts, it helps avoid such design mistakes in the first place.
Well, you care when you have an unexpected complex circular dependency or you need to refactor and you suddenly discover how complex your dependency graph has unintentionally become.
By "decoupling" and "modularization", I'm not talking about at a code-level by using interfaces. In my opinion, that kind of decoupling and modularization isn't very important, especially when it comes to application glue--because the glue (Spring) is what decides the backing impl's of all those interfaces. That's where the important dependencies lie, and that's where your big ball of mud accretes.
I just came across a controller in our code that had over 50 spring injected services. Our entire code base is only like 5 years old.
The problem is that this is my first job out of college... even if I know things are messed up, I don't really have an example of an application like this that is actually done right.
The same should apply to repositories unless it is absolutely necessary. It should ideally just know enough to manage itself
A service may reference other services if it needs to coordinate actions. We need to be careful not to make huge services that contains too much logic though
I have found that this pattern makes it not only easier to structure the application, but also much easier to make mocked tests.
Both Spring and Java EE are very featuresome because of a long period of evolution across multiple partial paradigm shifts (3-tier, CORBA, Web 1.0, App Containers, SOA, VMs, Web 2.0, Containers...).
I'm personally only familiar with Spring. And by "familiar" I mean I've learnt enough to be dangerous. But I've also learned to ask, before doing something: "Have the Spring team already solved this?"
Take, for example, retrying an operation. A slightly hacky way to achieve this is to nest try-catches. A slightly less hacky way is some sort of loop. Then we extract this functionality out. I dunno, lambdas maybe?
Or: I could use Spring-Retry[0].
That's just one example from my own experience.
It used to be that Java EE was it. And it had to be: the mega-buyers insisted that there were sufficiently rigorous standards that they could switch vendors. Every giant corporation bleeds gold to some ancient vendor and they have an immune reaction, nearly a spasm allergy, to vendor lockin[1].
Opensource tools kinda sail around this, because ... uh ... what vendor lockin? Who has you grasped, by what kind of delicate hair? Nobody. It's opensource. You can wait for community action, or engage a consultancy, or a related product company, or hire specialists, or sponsor something ... you have a wider range of options.
Oh sure, there's tech lockin, but that's true of any decision. Either you exploit the features of a platform and tie yourself to it, or you write a rubber-room abstraction layer and tie yourself to that instead, but at your expense.
In my observation as a 1st? 2nd? party, the Spring team pay close attention to the standards and support them. And, not coincidentally, many standards look -- gee, just awfully similar to something Spring had road-tested a few years prior.
I think a lot of the shade thrown in this debate is happening because of money. Pivotal backs Spring very strongly, because it helps to sell our version of Cloud Foundry (imaginatively: Pivotal Cloud Foundry).
Meanwhile, Oracle owns a lot of Java products, some inherited from Sun, as well as being the stewards of the Java EE standards process. Red Hat own a solid suite of Java EE-capable and platform products, a portfolio largely overlapping with Pivotal's, IBM's and SAP's.
In each of these cases there's a zero-ish sum-ish game underway. A company that bets on Red Hat doesn't bet on IBM. A company going all-in with Oracle (RIP your chequebook) isn't going to buy much from Pivotal.
Meanwhile, the Spring team are just writing software. I lurk some of their internal Slack channels. It's almost exclusively technical and usually very thoughtful. As a Pivot indoctrinated by Pivotal Labs, I appreciate having a very different example of how to develop high-quality software.
[0] https://github.com/spring-projects/spring-retry
[1] You will often find companies accusing each other this.
The idea that you would add a dependency to avoid writing a try-catch in a loop is absolutely bewildering to me. What next, Spring Pad? I can see it now:
@LeftPad(width = 50)
public String getNameForDisplay() {
return name;
}Not as simple as you might think. And if you're gonna be writing microservices you're going to be intimately familiar with retries.
Reliable retry is very important in some cases, and it's easier to test a retry-agnostic unit of code.
Edit: and I also rather like that it's declarative, rather than needing to infer intention or write my own boilerplate to explicate intention.
That doesn't make Oracle bad or Pivotal good.
Almost every time the answer for me has been "it seems like they have, but they don't handle a critical edge case of some kind and cannot be extended to, and thus their solution is totally useless."
I have also found them responsive to feedback.
I see two import aspects that Spring provide: first, dependency injection/inversion of control as basis and then a way to 1000+1 things "right".
Dependency injection is important because it provides a healthy way to compose an application from many components or services. It's not exclusive to Spring of course, you can do DI/IoC without Spring (even without any framework), but it's baked into the very nature of Spring.
Next, Spring provides "right" way to do a lot of more-or-less standard things. You have solutions for ORM, REST interfaces, security, batch processing and a thousand more tasks. From my experience, Spring solutions are mostly good or very good, at least much better than I'd design in a limited time frame. This gets me faster to the working solution.
I also don't thing that Spring brings that much of a complexity overhead. Once you got past the first threshold (like, you can assemble you application from a few components) you're good to go. Ok, you will eventually need to learn concepts of individual Spring components (like ORM or security), but you'd need to learn them anyway.
Eventually you'll need to fight Spring here and there, I won't deny it. This is normally related to the "automagical" stuff like autowiring or autoconfigurations - things which make your work easier in 98% but might make you crazy for the rest 2%.
- Not all Spring projects seem to receive equal love. They can and do stomp on each other. - Spring's get-going-quickly seems to mostly come from serious design assumptions. Which is fine per se but they are not made clear which is not OK. - Spring is far too automagical. My latest bugbear is it deciding to change defaults depending on what it thinks I want. Bonus points for there being multiple settings that control the same behaviour only one of which can override.
You're not using Java anymore you're using Spring.
DI creates debugging nightmares by moving what used to be compile time checks to runtime. Fowler was wrong.
My viewpoint is, roughly:
1) DI, as described in the GoF book is a great pattern. You compose objects by passing other objects to its constructor as needed. These are the stored in final fields for the lifetime of the parent object. The pattern gives a lot of flexibility and handles a lot of use cases which it may otherwise be tempting to handle using the inferior concept of inheritance. It's simple, it's typesafe, it's easy to debug.
2) DI, as implemented by virtually any DI framework, is the Devil. Most DI frameworks is little more than a big dictionary mapping interface types (or magic strings) to implementation classes, along with some black-box magic to make it work (and bloat your stacktraces). It saves you the effort of calling constructors explicitly but any gains are lost as soon as you need to debug stuff. It shares a lot of traits with that other big dictionary where you can dump stuff for convenience: the global scope. It also has the same backdraws.
But anyway, this is a minority position so there is a large chance that I'm dead wrong and a small chance that history will prove me right.
Java is great, the best, when I'm trying to build something creative. It's like having access to an entire parts warehouse vs a toolshed for most other languages. Unlike especially JS, most libraries are well tested and most people agree on doing things a certain way. There's not a lot of unknowns, almost no flux, and once you know the rules you can focus on your problem and ignore the "ecosystem" almost entirely.
The complexity can be looked at from a different perspective, I see it as similar to Roman characters vs Chinese. Learning 26 characters is a hell of a lot easier but with a couple thousand you can say the same things with a lot less writing. Once you've gotten over the cognitive hill of learning the complexity it doesn't really get in the way. It's there when you need it, and you ignore it when you don't.
Contrast this to JavaScript where most advancements redefine workflows or parts of the language core libraries. It feels like the most exciting thing about JavaScript is how unstable it is, something that scares the hell out of me after working on 15+ year old code. What you trade off with complexity in Java is definitely less work than keeping up with js for more than a year.
This sort of became a js/Java comparison because there's a ton of similarity in the hype between them and their lifecycle so far. Java was not always so complex after all. You can see the complexity of the JS ecosystem and language growing steadily and I have no doubt that in five years it will be just as complex as Java. The complexity is driven by the big guys that actually need it, and being the big guys, they also have the most influence on what the language eventually becomes.
So to recap, no, for the vast majority of projects the complexity is definitely not justified. It's there so the language can support the 1% of projects that need all that. On the other hand, once you know your way around it's fairly easy to ignore those parts. What access to all this stuff really gives you is power. As a footnote, give JS some time and it will be the same. Any language picked up by corporate interests is basically destined for overengineering.
I saw plenty of overengineering even with C back in the 90's.
My biggest example was a server framework that used its own concept of opaque pointers all over the code, because they abstracted data access over shared memory to all instances of the server.
So any memory access to data structures storing server state required translating between handles and pointer all the time.
So it's good when you need it, and it's unnecessary over-engineering for simple projects.
They include a lot of functionality, but beyond demo-scale applications, you will spend as much time fighting the framework as you save by using it.
The frameworks probably do save you time in getting started; Spring Boot certainly has a pretty smooth experience going from nothing to a running project with a controller, database, etc. But that's a tiny expense in the lifetime of a project.
EE might give you some kind of confidence that you can port your app between app servers (or different releases of an app server!). If you work in an environment where some idiot up the hierarchy has decided everyone should use app servers, that might be useful, but there's no point setting out to create such an environment.
You use Groovy, but it's pretty similar to Java and there's plenty of resources to bring you up to speed.
I can't really comment on Grails 3, funnily because the applications were written before 3.x came out and we haven't bothered upgrading them yet... :) I've only taken a brief look at what's involved in upgrading them, but other stuff has always been higher priority.
It's probably worth being familiar with both versions, because Grails had plenty of adoption before 3.x came out and 2.x is still being maintained.
Definitely not all batteries included, but a great minimalist web library to get started with.
The upside of Spring over Dropwizard or Play (or any of the lighter touch frameworks) is that it's so much more than an MVC or REST controller framework. The report in question is literally comparing it against the entire Java EE ecosystem. Spring is not a library or a framework anymore, it's practically an entire stack that you can pick and choose from.
Play and DW are likely to be easier to get your head around than Spring, there's a lot of magic going on in there and (not unlike getting into the JS world these days) you will find that setting the thing up from scratch takes a whole lot more time than you ever would have guessed. There's 1000 ways to do the same thing and sometimes doing it one way is a real issue later on, and worst of all it may not be apparent that that is the root source of some seemingly unrelated issue.
Spring Boot aims to solve a lot of that by taking an opinionated approach to how the project should be laid out, libraries, and default configuarations- more along the lines of a .NET MVC + database, etc. There's still the same amount of complexity and configuration, it's just hidden from you until you need to dig in.
I generally use Spring Boot to start greenfield projects these days, we deploy to AWS and their extra libraries for handling and working with that save a lot of boilerplate that's so common with Java.
I would suggest you get your feet wet with either GP or DW, but don't forget about Spring Boot down the road. I've gotten to the point where I've got a stack including Vault (secrets) + Spring Config Server (distributed config application) + a fleet of various Spring Boot microservices running in Docker on AWS and they (mostly) just startup and run the way I wanted them to. It's really awesome, and wildly overkill for that proof of concept API or personal project we would be doing a lot of, but allows me to run my company without a huge team.
https://playframework.com/download#examples
Should save you some time if you're looking to get started with JPA, different libraries, etc.
Also, this doesn't really get at the strengths of Play as a reactive application. For example, here's a talk where I discuss using Reactive Streams with Play to stream video through Play. It's about two years old now:
https://www.youtube.com/watch?v=zUB7GJMe_WY
Thanks!
AWS Lambda now supports Java, so perhaps we could consider API Gateway, Cognito and DynamoDB/Aurora as one of the new "frameworks". Google has their own competing offering as well, e.g. Cloud Functions. Firebase deserves a special mention as a rapid development framework, although at the moment it only supports Node.js. Like many frameworks it has pros and cons, but in my opinion full-on websockets support OOTB is a pretty cool pro.. especially since Lambda/Cloud Functions/FaaS can't do websockets on their own.
Configuration generally kind of sucks to manage, you don't get some of the role inheritance you can get from standard EC2/IAM, and forget running inside a VPC.
The idea behind using something like API Gateway + a Lambda for the various end points sounds awesome in theory, but going beyond contrived examples shows that we're a long way out.
Cold start time is a known issue but frankly, not that big of a problem in practice for many apps. Definitely I feel that dismissing this out of hand (I even got a downvote) is more of an emotional reaction to something really different and new than a rational one. There's nothing contrived about implementing a REST service with FaaS -- it's just simpler. It's worth giving it a serious look even as an experienced Java developer (as I am). There are a lot of benefits to the architecture, like minimal devops and tremendous cost effectiveness.
Don't think I'm dissing Lambdas (or anything like them in a non AWS context). There's a lot to love about them, not the least of which is that for small (in time to completion, request volume, and memory usage) units of work they are significantly cheaper.
There's a config management nightmare for everything but there are known ways to answer it for a traditional server/fleet approach. Run time configuration of Lambdas is still evolving and there aren't many options to choose from that are much better than just deploying the code and config as one as part of a delivery pipeline. It's workable now, but there are gaps. There aren't any gaps with running your own applications, it's literally what's always been done. There are certainly pain points, but the problem is already well understood.
There is also a huge number of tutorials from the horse's mouth on the topic.
It is a very lightweight framework with support for most of the plumbing a developer might want without imposing its will on anyone.
If you are not that worried about having a REST or HTTP or TCP front-end, LMAX disruptor is amazing as well.
I have had great success with the examples from TomEE (free) + the (paid) videos from Adam Bien (I normally shun videos but lke his).
The examples from TomEE often come in the form of something you can run with a single maven command.
I have been writing Java for 10 years now. I've used many different frameworks and libraries. But now all my new projects are Spring Boot projects.
After using Spring Boot for 2 years now, I'll never go back to EE.
I use Play 2 for most of my projects and frankly knowing what I know about Spring and J2EE (even though having never worked with them for the same reason) I couldn't really think of any reason to choose either of these outdated behemoths. With something like Play 2 and Slick/JOOQ you could run circles around these two.
Java compile time DI:
https://github.com/playframework/play-java-compile-di-exampl...
Java compile time DI using Dagger 2:
https://github.com/playframework/play-java-dagger2-example
Scala compile time DI:
https://github.com/playframework/play-scala-compile-di-examp...
Scala compile time DI with Macwire:
https://github.com/playframework/play-scala-macwire-di-examp...
Thanks!
It's certainly daunting if you're green to it and there isn't a grizzled vet around, but if you're trying to build for more than contrived simple solutions that you need to monitor, scale, and secure I don't know anything else out there that gets you so far down the road almost at the beginning.
In the original "Java Development with the Spring Framework" in 2005, it was argued that J2EE was far too complex in order to be effective. Spring was going to provide a simplifying framework to make development easier.
To me it's ironic how Spring has evolved in complexity over the years, but it's the first framework any of us turn to.
Opinionated frameworks are fine to get started quickly, but for larger scale companies, you are gunna want flexibility beyond that.
I'd also steer clear of anyone who can quote GoF or Fowler.
I never understood (the point of) EJB. Shamefacedly admit I was once a huge Hibernate fan. But I got over it.
Can't speak to Django nor Rails.
---
Prior joke: Spring is an stacktrace obfuscation framework.
New joke: NodeJS is a control-flow obfuscation framework.
Entity beans were always a disaster, and Stateful Session Beans had scaling problems, but Stateless Session beans by the time of EJB3 were an easy way to push logic out to the network while getting a whole bunch of stuff (pooling, scaling, failover, transaction management, security, etc) for free.
These days they'd be calling them micro services.
The main problem was combining everything into the big ball of mud that is J2EE. If they had let each component live and die on its own merits I think the ecosystem would be in a much better state today.
And then we all laughed until we realized half the trace was because it got logged and thrown and then logged again...
We find that our projects now have drastically less code doing the same types of things. Not only that, but the code we do write is predominantly declarative in nature. Clojure makes it much easier to separate the intent of the code from the implementation details.
Conversely, having less code means we're less attached to it. When it takes a 1000 lines to solve a problem, you tend to keep them around once you get a solution working. When you have 100 lines, it's much easier to throw them away and write a cleaner solution when you understand the problem.
Clojure projects are easier to debug and maintain thanks to pervasive immutability in the language. This allows us to safely think about parts of the application in isolation. When I come back to code that I wrote a few months ago and make a change, I know that the change is local and it's not going to affect another part of the project via side effects.
Clojure facilitates interactive development by providing strong integration between the editor and the REPL. When we're building new features, we're able to experiment interactively to see what approach will work best.
Since Clojure also runs in the browser with ClojureScript, we're able to use the same language for the full stack. This also lets us share code, such as validation logic, between the client and the server.
Meanwhile, we're still able to leverage existing Java libraries, infrastructure, and reap all the benefits of using the JVM.