My Experience As Lead Engineer Working On Backend

For the past year and a half, I had the opportunity to work on an incredible project for a major retailer brand as the lead engineer on the team.

I got involved in mobile development, backend development, and team management. During this period of time, we went through different stages; from discovery to production and production to release; with a lot of learnings from a technical and a management perspective.

Switching to Backend

As we were working on the project, we realized that we couldn’t hit our deadline on the backend side without support from an additional engineer. The main reasons were bad estimations upfront regarding the features we had to develop, and bad time and effort management during sprints where tickets would take longer than expected causing delays and stress on the team.

We had to react quickly in order to put the project back on track. What we did was a reorganization of the sprints to make them smaller but better defined — fewer stories, detailed description, a definition of done, unit test expectations — in order to slow down and speed up.

The other decision we made as a team was to have me switch from a pure lead engineer role to a backend engineer role. I had no prior experience in backend nor Javascript, so this was a big challenge for me. I was about to start working on a production project for a big brand with a large number of customers with technologies I didn’t master. Luckily the Senior Backend Engineer on the team helped me a lot getting up to speed. Pair programming and code reviews were a tremendous help for me to learn faster and become more comfortable with the platform quickly.

I will detail my approach tackling such a big challenge and share a couple of things I learned from backend that iOS could benefit from.

Learning Javascript / NodeJS

During my first week as a backend engineer, I spent the majority of my time working with NodeSchool.io. On their website, you can find several workshops around NodeJS all in Javascript. It was the perfect tool for me to learn the basics and quickly run code with clear feedback on why things wouldn’t work.

NodeSchool works as a Node package that you install on your machine and you can execute from your terminal. You can download the lessons that you want and run through the instructions. Each exercise has a suite of tests associated with it to ensure that your code is working as expected. This is great so you can have fast and clear feedback on why something isn’t working and you learn best from the mistakes you made.

The lessons I would recommend the most are:

  • learnyounode — NodeJS basics.
  • expressworks — Learn Express, the most popular NodeJS framework to run a backend server.
  • promise-it-wont-hurt — Learn Promises, our project is using them everywhere to handle asynchronous code.
  • async-you — Learn async/await syntax to handle Promises.
  • test-anything — Learn how to test your code.

Typescript

Our project used Typescript instead of Javascript. Typescript was created by Microsoft on top of Javascript in order to add types to it. The main goal of Typescript is to help Javascript developer self-document their code through types and make larger projects scalable, easier to understand and maintain. It also works with Javascript packages so you don’t have to worry about compatibility with existing libraries. Packages can even add Typescript type support as a plugin for better integration.

This was a huge help for me jumping on an unknown codebase. Thanks to interfaces defined in the code, I was able to understand what objects were being passed around. I wasn’t relying on reading inside functions all the time or reading someone’s documentation (often not descriptive enough, old or inaccurate anyway).

Coming from iOS and other object-oriented programming languages, Typescript was a blessing. I could create classes and interfaces, have dynamic type checking, things you don’t get with Javascript. I would highly recommend anyone thinking about using Typescript to do so. I truly believe this was a great decision and a huge time saver in the long run. Be careful though because Typescript is still built on top of Javascript, so the same Javascript weirdnesses apply to Typescript (looking at you == vs ===). Typescript is no magic since it transcompiles your code into Javascript before the code is being transformed into machine code, always keep that in mind.

In terms of IDE, Visual Studio Code from Microsoft is probably the best option out there to pair with Typescript.

Learning MongoDB with Mongoose

MongoDB was the database solution that got picked for the project for its simplicity and speed. I had no experience working with MongoDB nor NoSQL databases but it was fairly easy to understand. We used Mongoose Javascript package to define our data schemas and implement all the database I/O functions.

The best recommendations I can make is to carefully read both MongoDB and Mongoose documentations as they are pretty explicit on how to query and manipulate data. MongoDB provides query examples so you can play around with them and get familiar with the way MongoDB works. Mongoose provides code examples on how to connect to your database (it is using a shared connection throughout your project instance which isn’t something I expected), set up your data schemas and query data.

One software that helped me a lot was Robo 3T. It allows you to connect to your database and provide you with a UI to visualize your data and query it. It is very convenient for the day-to-day work to play around with MongoDB queries to experiment as you learn. Robo 3T paired with MongoDB scripts is probably what helped me the most understanding and getting familiar with MongoDB and its syntax.

Overall I liked working with MongoDB for its simplicity. The support is great in the community and you’ll find plenty of tutorials and guides. Robo 3T MongoDB scripts are the way to go if you want to learn how queries work and Javascript/Typescript has great support through the Mongoose framework.

async/await

Since we were building a backend server, we had to deal with a lot of asynchronous code in order to avoid blocking the main thread and support multiple requests at a time.

We used Javascript promises with the async/await syntax everywhere in our code and it was great! Promise is a pattern that basically certifies you that, at some point, you will get data back from your asynchronous execution or an error if something happens. You can chain promises using then() or catch errors using catch(). From there the Javascript team added async/await syntactic sugar to avoid writing callbacks and instead tell the compiler that here there is asynchronous code happening so you can simply do it in 1 line of code. If you want to catch a potential error you can encapsulate your code around a try/catch block.

I was used to the callback syntax in iOS (also called completion blocks) but when you need to chain multiple asynchronous events (and even worse when you have to wait for one to execute before you can move on to the next one) code becomes very quickly a disaster — very hard to read, very hard to maintain and prone to errors if you forget to support an asynchronous error somewhere.

Let’s see the difference between a callback and async/await syntax:

func loadAPI(parameterID: String) {
    myAPI.getData(parameterID: parameterID) { response, error in
        if let error = error {
            // Handle error here
        } else {
            // Handle response here
        }
    }
}

func loadAPI(parameterID: String) {
    try {
        let response = await myAPI.getData(parameterID: parameterID)
    } catch (let error) {
        // Handle error here
    }
}

You can see how cleaner the code is, especially when you have to chain asynchronous calls:

func loadAPI(parameterID: String) {
    myAPI.A() { response, error in
        if let error = error {
            // Handle error here
        } else {
            myAPI.B() { response, error in
                if let error = error {
                    // Handle error here
                } else {
                    // Handle response or make another async call
                }
            }
        }
    }
}

func loadAPI(parameterID: String) {
    try {
        let A = await myAPI.A()
        let B = await myAPI.B()
    } catch (let error) {
        // Handle errors from A and B here
    }
}

Here the error management is centralized and simplified, the code reads very easily and you write less code for the same result. If A() and B() were to run in parallel, you could use Javascript Promise.all to achieve that:

let resultsFromAandB = await Promise.all([A(), B()])

I cannot wait for iOS to have similar support for asynchronous code. There are some discussions happening in the Swift organization. Hopefully, Swift 5 or 6 will bring support for Promises and async/await natively in the language soon as it provides clearer code and tremendously helps to manage asynchronous errors.

Unit Testing

Unit testing in the backend is a fantastic experience compared to iOS. Not dealing with UIKit and purely data manipulation is great and helped me a lot writing small and efficient unit tests. We used mocha for our tests. It provides great syntax on how to create your test and all the test cases as well as command line tools to run your tests or only specific tests.

By using Typescript, I was able to replicate design patterns I was used to in iOS such as builders and factories. I could mock data through the Typescript interfaces we created and test behavior rapidly.

But because you ultimately work with Javascript, you can do fun dynamic things like spying your code to inject mock code to your application. We used Sinon.JS to stub and spy our code when design patterns were not applied and test that functions would be called as expected with the proper arguments etc.

Last testing technique I want to mention is snapshot testing using Jest. I knew about snapshot testing in mobile to test your UI by taking a screenshot and comparing with the current state of your view. What I didn’t know from the Javascript world is that you can snapshot API responses and compare that your code always returns the expected response. Instead of writing several lines of assertion in your tests, you can simply compare the response with a snapshot you generated and everything will be tested more efficiently.

Overall it was a great experience working on a completely different platform for multiple reasons. I learned a lot about different tools and technologies that can be applied to iOS. I also learned how backend engineers are working and what challenges they are facing — scale, uptime, performance, logging, monitoring, etc. Using my frontend background, I had a better idea of how to construct APIs and facilitate the work of my coworkers.