This is an interview with Small Improvement’s software developer Jesper Oskarsson about how the development team works together, what the tech stack looks like, and how big feature releases are tackled. If you want to read on, you find another developer interview here.
Hey Jesper! You recently had your 5-year anniversary at Small Improvements, so congratulations again. Could you briefly summarize your professional journey?
Thanks! Yeah, time flies… I’ve basically been programming most of my life now. I got interested in game development when I was a teenager, because I used to play a lot of video games and was fascinated by computers. After school, I studied software engineering with focus on game development. Before joining Small Improvements in 2017, I worked as freelance mobile developer for a couple of years.
Do you have a specific technological focus at SI?
I probably work more in the backend than in the frontend, but like everyone else in the dev team, I write code for the entire stack. Together with one of my teammates, I’m also responsible for overseeing security-related aspects of our application.
Can you tell us more about this security work?
Security is an important topic for us, because we handle sensitive personal data from our customers. Even though our security setup and mechanisms are pretty solid in general, there are always things to update and improve. For example, we have a HackerOne program, where ethical “white-hat hackers” try to uncover potential issues in our application. They report them to us, so that we can fix them, and in return they get a financial reward from us. Another example is a penetration test and security audit, which was conducted by external security researchers last year.
How much time do you spend with security-related tasks every week?
The workload depends of course, but it’s typically a few hours per week. I also don’t have to fix all bugs myself, by the way (laughs): we treat them as regular sprint tickets, which can be picked up by any developer. Our general philosophy is that responsibilities should be divided equally across the team: so certain people drive specific topics they are passionate or knowledgeable about, but at the same time, we want to avoid building up silos. It’s important for us that every developer stays in touch with all technical aspects of the application.
What programming languages and technologies does the app consist of?
We have a single-page-application in the frontend, which is connected to a monolithic JSON API in the backend. The frontend is mainly ReactJS, but there is also some older AngularJS code from the early days. The backend is implemented in Java using the Spring framework. It runs on Google App Engine in data centers in the US and EU. We are using Google Firestore as our main database, which is a document store, and then also some miscellaneous cloud services, such as task queues, or a secondary storage for our data warehouse.
What do you think about this tech stack?
Well, the grass on the other side of the fence always looks greener, doesn’t it? (laughs) But all jokes aside: I’m actually pretty happy with it. At the end of the day, our customers don’t pay us for what frameworks or programming languages we use. Instead, it’s important for them that the app works reliably and that it solves their business requirements. We have a content and stable customer base, so it seems like we made some good technical decisions.
Can you give an example of what works well and what doesn’t?
A good example is App Engine, which is Google’s managed cloud infrastructure that we use for hosting. The advantage is that we don’t have to bother with operational duties like scaling or database replication, because this is all taken care of by Google. The time that we save that way, we can invest into developing new features. On the other hand, App Engine has some limitations, so it’s sometimes annoying that we have to deal with or work around certain restrictions. I still think that the positive aspects outweigh the negative ones, though: when I look back at the last years, we only had a handful of minor incidents, even though we deploy multiple times per week. All in all, I would say that our infrastructure “just works” for the most part.
How does the team go about introducing new technologies?
Introducing new technologies is always a balancing act. On the one hand, we want to stay modern and take advantage of innovation, but on the other hand we can’t jump onboard of every new hype and refactor the entire code base all at once. I suppose our approach towards innovation can be best described as incremental: when someone sees an opportunity to implement something in a better way, they try it out and bring it up in our weekly developer meeting. It then gets reviewed and discussed in the entire team, and together we decide how we want to move forward. If we feel positive about it, and when it then stands the test of time, we gradually adopt it and make it our new “standard”. Examples of this from the past are our transition from AngularJS to ReactJS in the frontend, or, more recently, the introduction of a new testing library in the backend.
You recently worked on a new feature called “Automations“. What is this about?
The idea of the “Automations” feature is to make the lives of HR administrators easier. HR admins are the users within a company account who manage the HR-related processes for their companies’ staff, such as performance reviews or 360 feedback. Let’s say that company hires someone: the new employee needs to have access to the companies’ Small Improvements account, in order to prepare 1:1 meetings with their manager, or to participate in a feedback survey. Some of these administrative tasks can be quite repetitive, though, so with the new “Automations” feature the HR admin can create recurring workflows and schedule certain events ahead of time.
You implemented large parts of the new “Automations” feature. How did you approach this, and what did you find most difficult?
The most tricky challenge regarding the implementation was to carve out the right domain model that reflects the flows and rules in the code in a way that’s both neat and accurate. Although we had a clear vision of what the “Automations” feature should be capable of, there were many details regarding its usage, which we were uncertain about at the beginning. Our product manager and customer support team did a lot of research, so we started out by building a first prototype. That way, we could play around with the feature ourselves, and we also were able to show it to customers in order to receive external feedback.
How do you make these prototypes available? Do you have a separate testing environment?
We have a dedicated environment that we can deploy experimental features to. We only use this for internal testing, though. We generally try to integrate work-in-progress features into our main branch early on, to avoid long-running branches and the potential hassle that they can entail. We make use of a mechanism called “feature flags”. This effectively allows us to hide new functionality from end-users, even though the code is already merged in and deployed to production.
Is this a typical example of the feature development lifecycle?
For larger features, this is a common practice for us. It first starts on the machine of an individual developer. Once we have something working, we deploy it to our internal testing stage, where we can play around with it ourselves. If a prototype reaches a state that is somewhat stable, we can make it available as beta feature to curious customers. Finally, once we are happy with everything, we roll it out to the entire user base. During that whole process, we keep iterating on the functionality and continuously refine the implementation. For smaller features, we don’t need such a sophisticated procedure, of course: if a tweak or fix is straightforward enough, we usually release it right after merging.
Were you able to build the “Automations” feature on top of the existing infrastructure, or did you have to make bigger architectural changes?
Initially, we considered using cloud functions for running the scheduled background tasks, so that we could deploy and scale them separately. But eventually, we realized that it was better to implement the functionality within our existing system setup. This was also a matter of complexity: due to the iterative way we developed the feature itself, we wanted to avoid making bigger infrastructure changes at the same time. Another caveat was the burden of maintenance: while it would have been cool to dabble with new cloud technology, we felt that the benefits wouldn’t have been worth the additional overhead in this case.
Thanks, Jesper, for sharing these insights with us!
If you want to learn more about the development team, you find another developer interview here.