Object-oriented after working with functional programming

Sometimes I need to divide my knowledge in phases: when I started to program, I felt that C/C++ were the best languages to make a software. Then, I’ve began to work with Ruby and learned Test-Driven Development. I divide my knowledge “before TDD” and “after TDD”, because it literally changed the way I could think about software and tests in general. Now, I’ll probably make the same division: “before functional programming” and “after FP”, and I only discovered that things have changed this week: I just discovered I don’t know how to do object oriented programming anymore – and to be honest, it’s not really a bad thing: I also saw in practice how object-oriented programming expects you to write lots of boilerplate in most situations.

This realization came to me when I was working in a peculiar kind of problem – it’s basically a ruby code that needs to connect on messaging system and, when it receives a message, needs to persist it in a relational database. The problem is that this specific system have a strong consistency requirement, so the message could only be updated on DB before a specific event happens: after that, we can’t change it anymore, except for some specific fields. Also, as we’re working with messaging, we can receive the message multiple times, out of order, and all of those strange things that happens on Internet-land. So, to summarize:

The system will receive a message

It’ll UPSERT a record on DB, just with some identification fields (they’ll never change, and if they do, they characterize another message to be persisted – think about an external id)

It’ll BEGIN a transaction and SELECT ... FOR UPDATE my just upserted message

It’ll find, on another table, the rest of the fields on the message

If this record doesn’t exist, we’ll create it (and log)

If it does exist, we’ll check additional info, and decide: or we ignore it (and log), or we upsert (and log), or raise an error

Now, for the implementation. Because consistency is a must have on this system, I don’t want to expose for future programmers (be it myself or some of my friends) some code that’ll induce me to errors: after all, who wants to do with_consistency_check(msg) { Message.upsert!(msg) } when I can just do Message.upsert!(msg), right? So, I don’t want to allow people to be able to modify, find, update, or anything else outside of the with_consistency_check (or whatever name I decide). So, how to do it?
So, I began the Ruby code and the first hard part was naming things: should I name my class Messaging? It is a bad name because this class would need to connect to database and query records. Now, on OO Lingo, this could be a Repository, but this wasn’t really a database repository – also you can’t find arbitrary messages, and you’re constrained to the received message from the messaging system.

So, I decided to name it Message and change it later when I could get a better insight on how to work. Then I decided that the “class methods” would be for treatment, and the “instance methods” for the mapping of Ruby’s map to an object:

class Message
class &lt;&lt; self
def self.with_consistency_check(message)
upsert!(message)
DB.transaction do
yield(some_obj)
end
end
private def upsert!(message)
DB.execute(....)
end
end
def initialize(from_db)
@field1 = from_db[:field1]
...
end
end
# I want to use the code like the following:
Message.with_consistency_check(msg_from_messaging) do |cmd|
cmd.save_message!
if(some_condition)
cmd.start_processing do |msg_on_db|
do_some_processing(msg_on_db)
end
end
end

For those unfamiliar with Ruby, class << self will start a block of code that’ll allows us to define “class methods”: something that can be called via Message.something – it’s like a “static” method on Java. Now, the problem is that: I need to pass some kind of “object” to the block, but I need to pass it in such a way that I can only call these start_processing and save_messageinside the consistency check block: in other words, they should be private to the outside world. Now, how can I do it?

This object can’t be an instance of Message, because I’ll be adding functionality to Message that’s more than just mapping the object that came from DB. Also, it’ll become public, so every Message object that comes from DB will have these methods, and it can “escape” the block; I could ignore the mapping from the DB and return just a map of attributes, but then I’ll need to group methods that would be necessary for coercion, processing, and so on on other class… and then things become a mess.

Now, what I ended up doing was creating another class called Treatment, then grouping all functions inside it. The constructor of that class is private, so it’ll not be able to instantiate it unless you’re using the helper method:

class Treatment
class &lt;&lt; self
def with_consistency_check(message)
upsert!(message)
DB.transaction do
find_for_update!(message)
yield new(message)
end
end
private def upsert!(message)
DB.execute(....)
end
private def find_for_update!(message)
DB.query(....)
end
end
private def initialize(message)
@message = message
end
def save_message!
# all checks....
end
def start_processing!
# more checks....
yield
end
end
# People could still escape the security check by doing:
treatment = Treatment.with_consistency_check(message) do |t|
t
end
# but at least I feel safe that somebody will have common sense
# and imagine the the Right Way to use it is inside the block.

Not shown here were more issues like other things that were made public unnecessary, like the “dataset” (an abstraction to the DB connection/table), some auxiliary functions that had to be called for some sanity checks before each yield, and some other small things just because there’s no other way to solve this problem without complicating even more the code.

What about the functional code?

So, one can say: “it’s a difficult solution to a difficult problem”, and I agree. But in “functional-land”, I don’t need a object; heck, I don’t even have objects in the first place, nor classes, so the question “is this a class method or an instance one” is meaningless.

In fact, in the end I just want to run a bunch of functions in a specific context only. This could be solved simply using:

Wrong: transactions generate a new “connection to DB”, and they are discarded after the end of transaction. It is impossible to use these functions without being inside with-consistency-check, because there’ll be no “connection” to DB in the first place. The current implementation of clojure.java.jdbc throws a Null Pointer Exception when trying to query a DB that should be inside the transaction but it is not anymore – for example, if someone tries to query with:

Also, this new code don’t expose “datasets”, “connections”, “sanity checks functions”, because they are all private in their namespace. It also is smaller, at the cost of all methods expecting the arguments they want to operate with, but this can also be a good thing because I can “inject” mocked versions of these structures if, somehow, I want to change then in the future. At the end, I don’t even need to pass the same functions to save-message!, for example: if I detect that the message can’t be upserted, I can just pass a #(raise (some-error)) as parameter, and things will work the same: less boilerplate, again. In theory, you could use Ruby blocks/procs to make the same kind of code:

module Treatment
extend self # Ruby have no namespaces...
def with_consistency_check(message)
upsert!(message)
DB.transaction do
find_for_update!(message)
yield(
OpenStruct.new(
save_message: -&gt;(){ save_message!(message) },
start_processing: -&gt;(){ start_processing!(message) }
)
)
end
end
#...all other private code...
end
# We&#039;ll use it like this:
Treatment.with_consistency_check(msg_from_messaging) do |cmd|
cmd.save_message.call
if(some_condition)
cmd.start_processing.call do |msg_on_db|
do_some_processing(msg_on_db)
end
end
end
# But we can still escape scope:
commands = Treatment.with_consistency_check(msg_from_messaging) do |cmd|
cmd
end

I’ll leave to the reader if it’s really a viable solution to write it like this, and if any code review will approve this kind of code.