Why we re-wrote Ackly in Python from JavaScript

drew
3 min readJul 11, 2020

When Ackly was built it was done quickly like all MVPs to get from idea to deployed and in customers hands as quickly as possible.

In making an initial framework decision, we put together a small table comparing a few options from the officially supported/built tools.

Slack Framework Decision: 1–5 rating (least to most favorable)

It was clear that the Java framework was not an option and it was either Bolt or Python.

  • There was an order of magnitude less usage (imports on GitHub: 500 vs. 5000) on the newer Bolt framework as expected since it started in 2016 vs. 2014.
  • The documentation was also newer and seemed to be getting regular updates.

Initially we chose the Bolt framework. Most of Slack’s latest documentation also recommended starting with it. We were feeling adventurous, always up for learning and improving on our 3 score for language that we gave it (reasonable knowledge, not an expert).

This worked great! For the first month.

Here are a couple reasons why we chose to re-write Ackly in Python.

  1. Promise chains became very deep and refactoring them to simplify was a never-ending exercise.
  2. Inter-operability with Firestore (chosen database at the time) became more complex as the functionality expanded leading to 10s of functions just for database interactions.
  3. Await & Async in JavaScript are simply put: a bit of a mess in many libraries. Having to refactor or wrap a non-Promise supporting library with bluebird or similar to achieve your desired outcome (or to simply debug. what is happening) is time consuming and not the best use of time.

Promise chains

A typical promise chain for an inbound message or reaction would be > 10 promises deep.

.then() gets pretty old pretty fast.

When you refactor and clean this up a bit you still end up with having to resolve all of these (and debug. odd behaviors within).

Resolving Promises

Inter-op. w/Firestore

A typical database operation would be far more code than I would expect for a fairly simple operation (add an item). As more operations were added even with plenty of refactoring this continued to grow. With an ORM (hold opinions) or a better library this could likely be a lot less code and pushing less logic into the application layer itself (eg. check if an object exists). One could argue that this is more about the DB type (NoSQL) and I would tend to agree with that observation (would a separate post on that be interesting?).

Example Firestore Interaction w/JS

Async/Await & Promises

Half the codebase was using Async/Await and the other half was using Promises. Using Bolt you are generally using Async/Await so interactions with Bolt itself you generally try to keep this pattern. Mixing these patterns in a single file is an overhead that becomes illogical pretty quickly and it may break your brain.

For other libraries (Firestore) you may be using Promises and while you can do your best to keep them isolated you will inevitably end up with a mix.

Mixing Async/Await & Promises

Moving to Python

Re-writing to Python took two days of writing, testing, and rollout. Major highlights were as follows:

  • The codebase was cut in half — even when using an ORM (SQLAlchemy).
  • The Events API library was a major assist in making the code easy to read and follow.
  • Preference Learned: Debugging Flask was more preferential to debugging NodeJS both locally and remotely. Python on Google Cloud Run is more sensible to debug. than NodeJS.

Conclusion

This post covered a bit about our initial selection of Bolt and our move to Python as our choice of a Slack language framework.

If you are interested in learning more about Ackly, check us out over on ackly.io.

If you found parts of this post interesting or would like to discuss further, feel free to comment/reply or reach out on Twitter at @HeyAckly.

--

--