Java Counter Culture - Cultural Javism
What is Cultural Javism?
Haven't you heard about Cultural Javism? It's terrible! 😱
For those unaware, here's a taste of it.
You know when all our peers are writting classes like this, and this point you're to afraid to ask "why?":
WARNING: Please don't waste time reading the complete code of EnterpriseAddress
class.
public class EnterpriseAddress {
private String street;
private String number;
private String zipCode;
private String city;
private String state;
private String country;
public EnterpriseAddress(String street, String number, String zipCode, String city, String state, String country) {
Validator validator = (new ValidatorFactor).getValidator();
this.street = street;
this.number = number;
this.zipCode = zipCode;
this.city = city;
this.state = state;
this.country = country;
validator.validate();
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
When you could have this:
public record Address (
String street,
String number,
String zipCode,
String city,
String state,
String country,
) {
public Address {
new Validator().validate();
}
}
Or even this if you don't have records, and don't mind/want mutability:
public class SimlpleAddress {
public String street;
public String number;
public String zipCode;
public String city;
public String state;
public String country;
public SimlpleAddress(String street, String number, String zipCode, String city, String state, String country) {
this.street = street;
this.number = number;
this.zipCode = zipCode;
this.city = city;
this.state = state;
this.country = country;
new Validator().validate();
}
}
And who has yet to see a Java app like this?
When you could have a little app like this for the 25 users of your back office system:
But what is Cultural Javism anyway?!
Where are we going with this?
This culture is like your company's legacy code base, only from Java distinguished Well Fargo's engineers.
It's a culture from when everything was grass hills; everything was more complicated, including Java.
But let's not bite the hand that used to feed us, right? At some point, somewhere, it was all useful; there were no simple tools to solve problems that are trivial today. As an illustrious friend says,
"Many stones have been broken in today's backend engineering."
Sadly, many small companies believe they need to set up an extremely scalable, decoupled environment, with clean arch, ports & adapters, super modern, 500 abstractions, and complex. Why?
So as to avoid having a legacy system with technical debts.
For that I say, when does a system become a legacy? :
The exact moment you deploy it.
As my illustrious wife says,
"When you
git push,
your code has already become legacy."
How to fight legacy
Note that legacy and technical debt are different things; code that was sent to prod without testing because the boss was demanding it can quickly generate a technical debt, while legacy is simply something that is in the said company's history.
But I believe that a good way to achieve LEAGACY OBLITERATION! is to deliver your code as quickly as possible, make it as simple as possible, and, of course, tested, you bastard!
After all, if it doesn't meet your requirements, you didn't spend 2 hours of your day just deleting example files from the company's clean arch template. If there's a problem, your flow isn't abstracted into 20 layers and spread over 3 superclasses and 5 interfaces, so changing it will be simple because you made your system simple.
That's right, buddy; you don't need an interface for every service on your spring-boot app. You're not going to change your database.
And anyway, how much will those abstractions help you in the very rare event that you have to change databases?
Wouldn't placing your methods separately in another file do the trick?
At this point, you might be thinking, "But how am I going to scale a system for 100 million users in a simple way?".
Yeah, you won't!
The point is that your ERP system, which serves 5 companies with 25 employees each, doesn't need to worry about having a multi-cloud system like Netflix.
Your cron-job, which takes 5 seconds to run and executes 3 times a day, doesn't need 3 layers of ports & adapters.
Back to the point: Cultural Javism.
We talked about getters and setters, clean arch, ports & adapters . . . something about multi-cloud and ERPs with cron-job. What's the point of all that?
In simple terms:
People think Java is verbose, slow, and old because of the Javistc culture surrounding most Java projects you see on the market.
But don't worry, here's the answer to all your problems:
Java, especially modern Java, can be super simple and concise.
And by the way, Java is modernizing faster than ever after 2018, with 2 versions per year and 1 LTS version every other year.
To prove this, here's an example of a service that parses dates on the required AWS Cognito's format, using only Java's native APIs. You don't need to use Spring Boot for everything:
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
void main() throws Exception {
var server = HttpServer.create(new InetSocketAddress(8080), 0);
server.createContext("/", httpExchange -> cognitoParsedDate().ifPresentOrElse(
date -> send(httpExchange, 200, date),
() -> send(httpExchange, 500, "")));
server.start();
}
Optional<String> cognitoParsedDate() {
try {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
var dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzzz yyyy", Locale.ENGLISH);
return Optional.of(dateFormat.parse(new Date().toString()).toString());
} catch (ParseException e) {
return Optional.empty();
}
}
void send(HttpExchange httpExchange, int code, String response) {
try {
httpExchange.sendResponseHeaders(code, response.length());
var outputStream = httpExchange.getResponseBody();
outputStream.write(response.getBytes());
outputStream.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
It's not even 50 lines long; it's a single file and requires no external dependencies.
And to prove this point even further, here's the same service in JavaScript:
const http = require("http");
function getFormattedTimestamp() {
const date = new Date();
const dayOfWeek = date.toLocaleString('en-US', {
weekday: 'long',
timeZone: 'UTC',
});
const month = date.toLocaleString('en-US', {
month: 'long',
timeZone: 'UTC',
});
const day = date.toLocaleString('en-US', { day: 'numeric', timeZone: 'UTC' });
const year = date.toLocaleString('en-US', {
year: 'numeric',
timeZone: 'UTC',
});
const time = date.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
timeZone: 'UTC',
timeZoneName: 'short',
});
const [timeOnly, timeZone] = time.split(' ');
const parsedDate = `${dayOfWeek} ${month} ${day} ${timeOnly} ${timeZone} ${year}`;
console.log(parsedDate);
return parsedDate;
}
const host = 'localhost';
const port = 6666;
const requestListener = function (req, res) {
res.writeHead(200);
res.end(getFormattedTimestamp());
};
const server = http.createServer(requestListener);
server.listen(port, host, () => {
console.log(`Server is running on http://${host}:${port}`);
});
Still, on the example's domain, a "Cognito Utils" service, would it be difficult for you to modularize this?
"Oh, there's no way to maintain that code! It's everything in one file!"
Just split it up a bit, put the endpoints in a file, separate the business logic by niche, a file for Dates (yes, Cognito uses dates in this terrible format), and one to deal with SRP; you can have a file to generate keys for SRP validation, another for the logic part, sprinkle some little interfaces here and there and you are good to go!
Do you really need a clean arch template for everything?
Take ways
For those who agree with the Rebellious nature of Javistc Counter Culture, that's it; you've got the point: get yourself into a startup. 👍
And for those who disagree, if you're a great lover of clean arch and clean code, I'd like to make a few proposals for you:
Make a simple version of what you do at work, using only what the latest Java has to offer. It won't be that difficult.
After that the next step would be:
Refactor the parts where the Java native APIs aren't "ergonomic", put in loose Spring packages or any lib from the maven repository and JitPack that you think is cool.
At this point, you've probably already been brainwashed by the JCounter Culture, so the last step toward true freedom from 90's conglomerate's way of life is:
When you need to make a little script, for anything, that script for parsing a log, scraping data, even a CLI, in short, anything simple that you do with that interpreted lang, like JS or Python an so on, try to study it and do it with your friend, the little Java lang 🥹, you'll see that it's not that complicated, in some cases it may even be easier than with that interpreted lang.
Finally, for those who didn't get the point, call me on Xwitter, BlueSky, Github, Linkedin, Smoke Signal, or anywhere you want. Let's exchange ideas. <3
Thank you so much!
Continue reading about related topics:
Computaria: The lies you were told about OOP