An Example of Overengineering: Keep it WET
- April 28, 2021
- 1724 Unique Views
- 2 min read
This week's post is pretty short. I've already written about overengineering, but this adds a personal touch.
I had to rewrite my Jet Train demo to use another data provider, switching from a Swiss one to a Bay Area one. One of the main components of the demo is a streaming pipeline.
The pipeline:
- Reads data from a web endpoint
- Transforms data through several steps
- Writes the final data into an in-memory data grid
Most of the transform steps in #2 enrich the data. Each of them requires an implementation of a BiFunction.
These implementations all follow the same pattern:
- We evaluate the second parameter of the
BiFunction. - If it is
null, we return the first parameter; - if not, we use the second parameter to enrich the first parameter with and return the result.
It looks like this snippet:
fun enrich(json: JsonObject, data: String?): JsonObject =
if (data == null) json
else JsonObject(json).add("data", data)
In the parlance of Object-Oriented Programming, this looks like the poster child for the Template Method pattern. In Functional Programming, this is plain function composition. We can move the null-check inside a shared function outside of the bi-function.
fun unsafeEnrich(json: JsonObject, data: String?): JsonObject =
JsonObject(json).add("data", data) // 1
fun <T, U> nullSafe(f: BiFunction<T, U?, T>): BiFunction<T, U?, T> = // 2
BiFunction<T, U?, T> { t: T, u: U? ->
if (u == null) t
else f.apply(t, u)
}
val unsafeEnrich = BiFunction<JsonObject, String?, JsonObject> { json, data -> // 3
unsafeEnrich(json, data)
}
val safeEnrich = nullSafe(unsafeEnrich) // 4
- Move the null-check out of the function
- Factor the null-check into a
BiFunction - Create a
BiFunctionvariable from the function - Wrap the non null-safe
BiFunctioninto the safe one
We can now test:
println(safeEnrich.apply(orig, null)) println(safeEnrich.apply(orig, "x"))
It works:
{"foo":"bar"}
{"foo":"bar","data":"x"}
When I finished the code, I looked at the code and thought about the quote from Jurassic Park:
Your scientists were so preoccupied with whether or not they could, they didn't stop to think if they should.
I'm no scientist, but I felt it applied to the work I just did. I realized that I refactored in order to comply to the DRY principle. When looking at the code, it didn't look more readable and the code added to every function was minimal anyway. I threw away my refactoring work in favor of the WET principle.
There are two lessons here:
- Think before you code - this one I regularly forget.
- Don't be afraid to throw away your code.
Originally published at A Java Geek on April 14th, 2021
Don’t Forget to Share This Post!
Comments (2)
Tobiloba
3 years agoI see that you save the point of interest as text in the DB but the response gotten from ChatGPT is JSON. Does this mean you convert the response into string using libraries like gson before saving it in the database?
Denis Magda
3 years agoHey, The response is a String object in the JSON format [1]. The repository takes this JSON string as is and stores to the database [2]. Presently, Spring Data auto-generates the CREATE TABLE statement on the startup and sets the "point of interest" column's type to "text" (or "varchar", don't remember). However, it's always possible to ask Spring Data to use the "json" or "jsonb" type for the column if you wish to query the JSON at the database level. Finally, Vaadin displays a list of PointsOfInterests. Those are generated using the org.json library [3]. Let me know if you have other questions. Hope this helps. [1] https://github.com/YugabyteDB-Samples/budget-journey-gpt/blob/main/src/main/java/com/yugabyte/com/TripsAdvisorService.java#L103 [2] https://github.com/YugabyteDB-Samples/budget-journey-gpt/blob/main/src/main/java/com/yugabyte/com/TripsAdvisorService.java#L74 [3] https://github.com/YugabyteDB-Samples/budget-journey-gpt/blob/main/src/main/java/com/yugabyte/com/TripsAdvisorService.java#L114