But spring-boot has an almost zen like quality once you get that it favor convention over configuration. When I was a Java developer, I'd usually use spring-boot with the following dependencies to make the experience better:
- lombok: to generate the boilerplate: constructors, getters, setters, equals, hashcode...
- mybatis-spring-boot-starter: mybatis is a sql resultset mapper, you write the sql and it maps the results. I find that ORM like Hibernate or Eclipselink are a complexity trap: easy things are really easy but hard things are incredibly complicated, mybatis avoid that.
Of course it's ambiguous because Java can mean both the language, and the wider ecosystem of libraries, languages, JVMs, tools etc.
Instead, Lombok allows me to mark fields with an annotation and then use getters and setters that never actually exist in my code. In practice, it's just adding an annotation in places to control functionality, which is not what I usually think of when I read "generate boilerplate."
I couldn't disagree more. I can understand disliking the fact that we need getters/setters in the first place, or perhaps dynamic things like annotations which aren't "normal" code, but static code generation is something that the industry absolutely should embrace.
Model driven development should be more common.
For example, if you can just draw a few ER diagrams and have MySQL Workbench forward engineer SQL migrations for you to check them over (especially if you need to change 20 tables), why shouldn't you take advantage of that? Having a model of your schema that you can generate from the live schema and then transform it into either a set of fresh migrations or just a delta for bringing an older schema version up to date.
I've actually used these two approaches to save bunches of time for personal projects in the past, even though I took the SQL output and put it into dbmate migration tool.
Reverse engineer: https://dev.mysql.com/doc/workbench/en/wb-reverse-engineer-create-script.html
Forward engineer: https://dev.mysql.com/doc/workbench/en/wb-forward-engineering-sql-scripts.html
But databases are just one example. Remember SOAP?Despite being hard to use, one of the best points of SOAP was WSDL - Web Services Definition Language files which allowed you to have a fully functional description of a particular API contained in a single file. Back when REST started replacing it, there was also WADL, but that didn't really go anywhere. The claim that web service endpoints (REST or otherwise) should be largely dynamic/schemaless is basically a lie, because in most languages you'll indeed want to work with particular fields for the objects that you expect to be returned, or domain classes that shouldn't be that different in practice from what you expect.
The beauty of WSDL was that you could get the file from a live version of an API, feed it into something like SoapUI and get a fully working API client, upon which you could build a test suite. Or even better, generate client code for your language of choice, so instead of making HTTP calls and wondering about how to initialize the client, you could start using library code much sooner, with parameters and methods already created for you.
Some time later, OpenAPI came along, but the wisdom of SOAP was basically lost, since it took a while for projects like OpenAPI Generator to pop up and even now the approach of generating client (or even server!) code based on some specification seems to be utterly lost on the industry.
WSDL: https://en.wikipedia.org/wiki/Web_Services_Description_Language
WADL: https://en.wikipedia.org/wiki/Web_Application_Description_Language
SoapUI: https://www.soapui.org/
OpenAPI: https://en.wikipedia.org/wiki/OpenAPI_Specification
OpenAPI Generator: https://openapi-generator.tech/
Want more examples? What about database schemas and mapping them to your ORM/other persistence layer solution? I think if you're writing your own model code, you're doing something wrong, regardless of the language that you use. You'll probably mess up or miss relation mapping with something like Hibernate, will miss out on some comments for autocomplete in Laravel and just generally will have an inconsistent persistence layer that will make you waste time.In most cases, starting with the schema first and using one of the available generation solutions to fill in the application side of the persistence layer seems like the only sane options. Sure, some might prefer to handle migrations in the app side, like Ruby's Active Record Migrations, or something like Liquibase, which are also passable approaches, as long as you don't create a bad schema just so it fits your application.
Java JPA entity generator example: https://github.com/smartnews/jpa-entity-generator
Java generator to get DDL from JPA: https://github.com/Devskiller/jpa2ddl
PHP (Laravel) generator to get models from schema: https://github.com/krlove/eloquent-model-generator
Ruby Active Record Migrations: https://guides.rubyonrails.org/active_record_migrations.html
In most cases, when you integrate two systems, APIs or just bits of code, one side should be the source of truth and the other should match it as closely as possible (problems with data types aside). Somehow the industry doesn't really know how to do this well, though thankfully projects like gRPC prove that it's perfectly doable in a modern setting.When this isn't done, you end up with either technologies that are a bit too green to be used successfully (e.g. picking GraphQL to supposedly deal with dynamic data and then being a month late with actually shipping), or having to waste your own time writing "boilerplate" that you will actually need, because although it could and should be generated, it is actually needed (to query that API, access that database, migrate that schema).
I do agree in regards to the pointless Spring XML boilerplate, which is indeed useless.
@Entity
class User {
@NotBlank
String username;
}
// Use site
public User addUser(@Valid User user) { ... }
as opposed to class User {
String username;
public void validate() throws ValidationException {
if (username.isBlank()) throw new ValidationException("username is empty");
}
}
// Use site
public User addUser(User user) {
user.validate();
// ...
} fun calc1(@RequestParam @NotNull @Size(max=10) @Pattern(regexp = "^\\d{4}-\\d{1,2}-\\d{1,2}$") date:String,
@RequestParam @NotNull @Size(max=5) @Pattern(regexp = "^\\d{2}:\\d{2}$") time:String,
@RequestParam @NotNull @Size(max=40) @Pattern(regexp = "^[-_'A-Za-z/\\d]{2,50}$") zone:String,
): Map<String, Any> {
Why stuff everything in the param list? No other framework I can think of proliferates annotations like this. Spring annotations being Java-based means I must suffer Java's ridiculous inability to handle regex metacharacters - even after introducing raw string literals in Java 13. Kotlin handles this perfectly but once I'm in Spring annotation-land Spring's touted Kotlin compatibility goes out the window.However, defining the validation schema in the parameter list is ugly too. Define it elsewhere and have it run through a validation middleware.
Just as ridiculous is the HttpSecurity method chaining “DSL”.