17 Comments
Feb 25, 2022Liked by Kent Beck

Interacting with 3rd party vendor APIs comes to mind for this pattern. You have no ability to change their API, it is immutable. It doesn't match up with what you're actually trying to achieve with your business logic. Write the interface to that API from the business logic *that makes the most sense for the business logic*. And within that sectioned-off layer/library/module, you can deal with the messiness of translating the data into the shape the API demands.

Expand full comment
author

Bingo!

Expand full comment
Feb 25, 2022·edited Feb 25, 2022Liked by Kent Beck

Similar to this one is the "beyond PEP8" talk from Raymond Hettinger. How to interface in python with all the python way of doing things, to a java API https://www.youtube.com/watch?v=wf-BqAjZb8M

An adapter layer.

Expand full comment
author

That's far better than letting Java conventions metastasize throughout your code.

Expand full comment
Feb 25, 2022Liked by Kent Beck

I agree completely.

I sometime also have the opposite problem, I cannot chose my caller but can chose the implementation and can split between "the weird things I am called with" and a splitted implementation on simpler types.

Something like "find version from branch name" called with a stateful github repository object and that needs to do some action on it.

Writing unit tests for this would be painful and I do not have the choice of the API I am called with.

But what I can do in the called function, is transform the complex object to a well known type, split the parsing done on a string in a separate function and in the called function, re-adapt it to the caller expected return value.

This constructs pure function helpers only using basic types and that can be tested in the documentation with https://docs.python.org/3/library/doctest.html

An easy way to increase coverage in a badly architecture code.

I use this a lot in existing software I cannot really change. Just creating some safe spots I feel safe to work with.

Expand full comment
author

That's a good one. I have that too but I didn't know how to explain it. Now I'll try. Thank you!

Expand full comment

I thought the same. An adapter from business concern.

Expand full comment

+1

this was also my first thought. And that's why I think it is an important refactor. Often I see 3rd party code in the middle of "our code" and when there is an update needed for the 3rd party code (for example aws java sdk 1.x.x to 2.x.x) you have to touch "all your code" instead of just the aws parts that are behind a nice interface. https://github.com/emilybache/DeliveryController-Refactoring-Kata might be a good example. Either in it's starter form because the email details (email has subject&body) are still leaking into the controller, or when one even inlines all the email sending code to the controller class and hides it in private methods to not make he update method too long ;)

Expand full comment
Mar 7, 2022Liked by Kent Beck

Leaky abstractions are good candidates. At some point we need to stop the leakage of internal implementation to the caller and this is a good way of arresting that. Another, perhaps related, is an API at the wrong level of abstraction. Some message queue API's come to mind that reveal the mechanics of the message broker and not the semantics of messages and queue.

This gets my vote for being included in the final copy because the application may be rare but the problem is fairly common.

Expand full comment
author

Can you give me a pseudo-code sketch of what this transformation looks like?

Expand full comment
Mar 14, 2022·edited Mar 14, 2022

I recently used RabbitMQ's Java Client and had to setup a queue for long running workers.

To setup a queue with the configuration needed, I had to create a connection factory, to get a connection, to get a channel, and then call this method.

https://rabbitmq.github.io/rabbitmq-java-client/api/current/com/rabbitmq/client/Channel.html#queueDeclare(java.lang.String,boolean,boolean,boolean,java.util.Map)

Ignoring the setup leading to configuring the queue, the queue config was still cumbersome for me.

As pseudocode:

Here's the old implementation...

configure_queue(string queueName, boolean is_durable, boolean restrict_to_connection, boolean autodelete_queue, map more_args)

... that was annoying for me to use ...

configure_queue("my_queue", true, false, false, { "x-max-length": 10} )

And this is what I would rather have used ...

configure_queue(string name, queue_configuration config) {

rest_of_config = collect (key, value) from config.entries where key not in ("durability", "restrict_to_connection", "autodelete_queue")

configure_queue(name, config.durability, config.restrict_to_connection, config.autodelete_queue, rest_of_config)

}

Expand full comment
Feb 25, 2022Liked by Kent Beck

This reminds me of Fowler's https://martinfowler.com/bliki/StranglerFigApplication.html. I like that there's echoes between large-scale and tiny-scale techniques like these, so I would love to see it included. Which puts me on the line for finding an example...

Expand full comment
author

I'm glad you said it. Saved me the trouble :wink:

Expand full comment

I spent some time looking through git logs of my open source projects. This was the only thing that stood out: https://github.com/kimgr/git-format/commit/8c67e25b2ed62f687666f30bd344a004594f9128. It's not a perfect example (very detaily, consolidates immediately instead of strictly doing new interface around old implementation, and then migrating) -- but it's real.

Expand full comment
Feb 25, 2022Liked by Kent Beck

I’ve used this when the application calls a stored procedure, the requirement changes and so there is a new stored procedure API that encapsulates the new behaviour (and so may require new parameters, though could be an overload). Since all API changes are made backwards compatible (including within the application code). We leave the original API and create a new one (called to support the new behaviour, when a feature flag is set). The new one, takes some additional action before delegating to the original implementation.

I hope I’ve understood this tidying correctly.

Expand full comment
author

I think you understand. The "tidying" moment is when you look at the original API and think, "that's not what I want to be calling, I want to be calling _this_ instead". Then you quickly implement the new API & call it. (You go back later to update all the other callers & then clean up the implementation.)

Expand full comment

Yes, please keep this in. I encounter so many places where this should have been done, and so many people who don't even know it's possible.

It also makes me think about wishful api design. Something I try to teach people the value of as well.

Expand full comment