Yes and no -- I often found the difference between a well architected and modularized (when possible) system and one that isn't is that the learning curve is more linear.
Frameworks that are large often have lots to learn in them, necessarily, but how that learning has to be done can differ. Good frameworks will be simple enough that you can almost ignore the ocean of complexity until you need to dive in, and when you dive in all of the pieces that you now must need to learn and know are almost obviously necessary.
For example, compare/contrast a system like express (nodejs) with a system spring in java. The func(req, res, next) paradigm gets you VERY far, but doesn't expose you to too much before you need to. Implementing middleware is much more delightful in nodejs (and other frameworks that adopted the func(req, resp, next) abstraction) than it is in Sprint.
Implementing simple middleware in Spring (boot) is pretty much the same. One class with one method that has access to req, res and the next filter. The @Component annotation is all it takes to enable the filter in your web stack.
@Component
public class MyMiddleware extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// your code
filterChain.doFilter(request, response);
}
}
I think this is a good example of the difference in complexity grades of both langauges/frameworks. That spring boot example is indeed pretty simple (for java), but to understand it, here's what I'm pretty sure has to happen:
1. Set up your spring boot project properly
2. Set up all the XML that supports your beans properly
3. Know how and when to use @Component
4. Know that OncePerRequestFilter exists, and/or to extend it, which method to override
5. Know to call filterChain properly
Most of these are trivial with some IDE support and maybe a templating school, and probably not even an issue after your second week with Spring boot (or spring in general), and it's an improvement over what life used to be like but compare this with the general jist of how to do it in JS:
1. set up expressjs properly (usually a two-liner). It won't have things like @Cachable that spring boot provides to you out of the gate, but it generally has enough to get you up and serving requests.
2. Know what req/res are (the fact that calling res.send(...) sends stuff and .ends as well is arguably nontrivial) and how to use next
3. Know what a function is.
the code snippet I'm thinking of is:
app.use(function(req, res, next) {
// your code
next();
})
It is "pretty much" the same, but how much work it is really depends on who you ask -- you can get up to speed with a simply built expressjs app in tens of minutes (many express apps follow a very simple setup pattern), but I've never seen that happen in a spring boot app for an absolutely fresh developer, even worse if the codebase is transitioning from spring to spring boot or has some other hacks in it.
Spring Boot is marvelously simple one of it's best parts (it almost makes me want to use Java for a greenfield project), but there's so much noise in that code -- it should just be a function -- IMO all started with the decision in 1.1 (??) to choose Runnable over lambdas. Most of the complexity I'm ribbing Java over is just due to how you write the langage -- java on it's simplest day just can't be as simple as some of the stuff that came after it, since they had the benefit of hindsight.
1. setting up a Spring Boot project is just File/New Project/Next/Next/Next, or download a starter project from https://start.spring.io
2. It's 2018, we don't use XML anymore, a Main class annotated with @SpringBootApplication is all the configuration you need (and autogenerated by step 1)
3. All you need to know about @Component before you get started is if you annotate a class with it, it will get instantiated with all its dependencies and made available for use in the container
4./5. Yes, to know about Filter, the filterChain and how to use it might take some minutes looking into the documentation or stack overflow, but that is what you are going to have to do anyway if you are going to use a framework.
I put knowing how to use @Component and figuring out what you need from the docs on the same level as knowing about the (req, res, next) pattern and knowing about async/callbacks/promises.
I agree that Java is verbose and has much ceremony but modern IDE's take away a lot of that pain. For my filter example the only things I typed are:
* the class name
* extends OPRF<tab>
* <alt><enter> <enter> to generate the 1 method to implement
* the `// your code` comment and filterChain.doFilter()
I'm not sure what you mean about choosing Runnable over lambdas. Runnable `is` a functional interface so anywhere you can use a Runnable, you can pass a lambda (or method reference)
I'm pretty spoiled when it comes to ergonomics for libraries I choose to use, and java servlet-based libraries don't do it for me these days.
Last I touched java, Jersey was really exciting to me (https://jersey.github.io/) -- netty + good annotations/flask-like syntax + access to the beast that is the JVM is everywhere I want to be. Jersey doens't necessary force you into the servlet pattern IIRC?
No. "Make the easy things easy, make the hard things possible." Frameworks are usually good at making their target use case easy; they're worthless if they fail at this. The risk is when you need something outside the box, do you need to find a nicely wrapped framework widget to plug in, or can you easily wrap it yourself? Or is the abstraction light enough that you can easily drill through to the implementation layer?
It's about optimizing for the easiest case vs the harder cases in design. This is the design space that answers the question as to how serious the curve is. Serious curves require a leap in complexity, whereas other approaches can have more incremental increases.
Authentication was a fine example. The canonical example in my mind though is Visual Basic vs Delphi. VB (version 3 era, for Windows 3.11) required that you write your components in C++. You could use the framework in Basic but to customize it you needed a leap up a cliff in complexity. Whereas Delphi's VCL was written in Pascal just like your own code, the source shipped with the product, you could step through it, and you could even make a copy of the source and modify it as a step on the way towards writing your own component.
With PHP frameworks, I can monkey patch things by grabbing a text editor and just editing the functions I need fixed.
On the other end of the spectrum, looking at the Windows compile instructions for popular C++ products makes me reach for a bottle, of Advil or Absinthe.
Yes, I can customize any component assuming I have the source (and modification rights), but for some components, the customization comes naturally, with others, just getting a runnable binary is a whole-week ordeal.
Frameworks that are large often have lots to learn in them, necessarily, but how that learning has to be done can differ. Good frameworks will be simple enough that you can almost ignore the ocean of complexity until you need to dive in, and when you dive in all of the pieces that you now must need to learn and know are almost obviously necessary.
For example, compare/contrast a system like express (nodejs) with a system spring in java. The func(req, res, next) paradigm gets you VERY far, but doesn't expose you to too much before you need to. Implementing middleware is much more delightful in nodejs (and other frameworks that adopted the func(req, resp, next) abstraction) than it is in Sprint.