Refactor Hell

by Zyrn

Tags:

refactoringdata modellingjavascriptfluttermobile applications

Once again, this month I revisited an old project and improved it. This project was the notifications app I developed in November last year, which I discussed at the time in this article.

The largest change on all was a complete overhaul of the backend architecture, which was centered around the change for a singular SQLite database on deployed server to using Google Firebase's Firestore, offering a host of benefits such as:

  • Cloud Data Redundancy
  • Security (SQL injection style vulnerabilities)
  • Scalability
  • Increase Maintainability (no need for a RESTful API)

This change in database and also database paradigm, also resulted in a much more manageable code base as I switched from a loose imperative coding style to an object oriented design which more closely matched the document style database. This meant there were a lot less confusing and poorly design bits of code as there was a clear data model of which the class models could imitate.

The removal of the RESTful API was particularly nice as it was poorly structured, using no extra layers of abstraction from the minimalistic design of Express.js. One example of this poor code quality was the heavy use of try-catches around a single line of code or using await without any error handling.

An example of poor asynchronous code and error handling

// routes/topic.js (API endpoint for handling subscriptions to topics)
router.get('/', async (req, res) => {   
try {
    res.json(await db.getMemberGroups());
} catch (err) {
    console.error(err);
    res.status(500).send("Internal Server Error: " + err);
}

await db.unsubscribeFromGroup(token, topic).catch(err => {});
});

An example of event based programming, which is not subject to some errors which async / await introduced.

// lib/Jobs.js (controller for issusing notifications)
this.ref.onSnapshot(querySnapshot => {
    querySnapshot.docChanges().forEach(change => {
        if (change.type === 'added') {
            this.jobs.set(change.doc.id, { data: change.doc.data() });
            if (change.doc.data().isSubscribed) {
                this.registerJob(change.doc);
            }
        }
        if (change.type === 'modified') {
            this.jobs.set(change.doc.id, change.doc.data());
            if (change.doc.data().isSubscribed) {
                this.registerJob(change.doc);
            } else {
                this.deregisterJob(change.doc);
            }
        }
        if (change.type === 'removed') {
            this.jobs.delete(change.doc.id);
            this.deregisterJob(change.doc);
        }
    });
});

As a result of this change on database, I also upgraded the security of the authentication within the system, moving from manually issues API tokens, to more secure, flexible, and accessible tokens issued using Google Firebase Auth.

These major changes in the backend, meant the frontend needed a significant re-write. I used this opportunity to upgrade to Flutter 2, using null safety.

made with by zyrn