We at Taxdoo use an array of testing techniques: unit tests, integration tests, end-2-end tests, contract testing, browser-based tests, tests against Docker environments as well as tests against real AWS environments, canaries, … and of course some manual testing when automating a test simply isn’t feasible.

Looking at the possible techniques, browser-based testing often gets a bad reputation as being slow, fragile, and needing constant maintenance. It is the technique that can provide the strongest guarantees though and is the easiest to understand (and be valued) by non-technical stakeholders. For example, a browser-based test might ensure that a certain critical functionality always works, even taking browser specific quirks and subtle aspects like infrastructure specific aspects into account. While browser-based tests have the major benefit of being as close to actual user behaviour as possible, they have the major downside that most engineers are not familiar with them. They pose a new set of tools and techniques that an engineer has to master while for example unit tests are much closer to the coding techniques an engineer uses anyway.

In order to minimise effort and avoid typical challenges of end-2-end tests, we have developed a set of best practices and troubleshooting tricks we are happy to share here.

Maintainability of Browser-Based Tests

A core principle of automated testing is to create tests that test behaviour, not implementation.

This is in particular true for browser-based tests as UIs are often subject to change, both because of voluntary changes in design and layout or involuntary changes by updating the component libraries in use. Many developers have had bad experiences with browser-based tests because they seem to easily break as soon as someone changes a control or the timing of underlying backend functionality changes. This however is not inherently a problem of browser-based tests, but incorrect usage of them.

In fact, browser-based tests can be among the most stable tests requiring the least amount of maintenance while performing large refactorings in the underlying implementation, precisely because they act on the “top layer” and can be unaware of large architectural or implementation changes. Changes in APIs, serialisation formats, code structure/organisation, database schemas and so forth often cause a need to adapt unit tests. But when used correctly, browser-based tests only require updating when the actual behaviour changes.

There are two key principles to achieving stable browser-based tests that many software engineers get wrong:

  1. The correct usage of the data-testid property to identify elements of a page instead of using HTML ID properties, CSS classes, or textual content.
  2. Writing tests in an event-driven way that is independent of the actual timing of the application.

The first is rather obvious: the exact HTML tags and properties used to render an application are often in a flux while the application’s design evolves. It is quite common to move controls to different places, change margins, etc. Similarly, user-facing texts get updated and typos corrected. In these cases, we want the tests to still be valid and run without the need for change. If on the other hand a text control is replaced with a drop down, the actual behaviour of the application changes and it is to be expected that browser-based tests would need adoption. Still, when using data-test ids, only the test code that manipulates the control has to be adopted instead of the code that tries to locate the element, making what needs to change much more transparent.

Using an event-driven approach to testing seems to be a much less commonly known practice. In practice, many browser-based tests incorrectly use sleeps/waits to wait for a predetermined amount of time before continuing. This not only unnecessarily slows tests down but also can be the source of flakiness as tests might get executed on different CI job runner machines from run to run.

In an earlier generation of browser-based testing tools (such as Puppeteer), the commands often waited for network traffic to stop as an indicator that an operation such as pressing a form submit button was completed and the test could continue. This caused numerous hard to diagnose timing issues.

A much better solution is to actually wait for application events, such as a confirm notification, that are shown to the user or the changed data becoming visible in the UI. If that is not possible, our recommendation is to use Cypress intercepts to define an alias for them, and then wait for the respective XHR to be complete. This is probably the most robust way to design tests but it has the downside of needing updates if the request’s API path changes (which luckily only happens infrequently).

Using Custom Commands & APIs to Create Test Data

A technique that has shown great promise internally in Taxdoo is to have tests create their own test data using our internal service APIs and wrapping this in Cypress custom commands. From a test perspective, this leads to easy to understand code, e.g.

cy.createClient(…);

cy.addToAccount(…);.

cy.addFiling(…);

but it also keeps the tests easily maintainable. Since the commands just fire simple GraphQL queries against our internal APIs, any changes in database schemas or similar that would break tests that use SQL inserts to add test data no longer are an issue. Any new logic added to the client creation code is automatically invoked as well every time the test runs, as it just uses the internal APIs.

Compared to direct database insertion of test data, the effects on test runtime are mostly negligible and as a nice side effect, the tests also notice if there are any unexpected breaking changes in our APIs, something that can be very relevant because we expose some of our APIs to our customers as well.

To wrap up this post, here is a list of dos and don’ts we have developed internally for when we write tests using Cypress.

Things to Do

  • Keep defaults: Be wary of changing Cypress defaults lightly, such as disabling test isolation, extending timeouts to higher than default values, changing the default folder structure because most of them exist for a reason and the majority of applications will work fine with them. Having to change a Cypress default can be an indicator that the application does something unusual and would benefit from moving to common web application standards.
  • Don’t assert for things that can be checked implicitly: There is no need to validate the test has navigated to the right URL if it then continues to search and click on specific elements anyway. Adding such asserts just adds more points that can break during refactorings that don’t actually change the applications behaviour. Use .should(…) for the things that really matter and are at the core of the test. That makes the intent more clear and the test easier to read.
  • Have tests create their own data: Don’t rely on predefined data in some database (e.g. a specific test user in a test environment) but – where feasible – have tests create their own data. This a) allows the tests to actually modify the data without side effects to other tests and b) doesn’t run the danger of some external effect changing the predefined data and thus failing the tests for unrelated reasons.
  • Fixtures & Config: Put predefined values such as environment variables or subsets of test data into their appropriate places in the Cypress structure instead of hard coding them in the test themselves. This makes it easier to run the same test in different environments such as locally, in a Docker environment, and in an AWS test environment.
  • Data Test IDs: Use the HTML attribute data-testid (be careful of consistent spelling, not test-data-id, not data-test-id) to mark frontend elements and then reference them from the Cypress tests. Values should be defined as a constant that both the frontend and tests use instead of duplicating the string in the tests.
  • Create test data via APIs: don’t do raw inserts into databases if it can be avoided. Using APIs a) is usually less subject to change and b) also nicely tests that the underlying service doesn’t introduce breaking changes without anyone noticing.
  • Clear data before test run: if the test is not only running in a temporary Docker container but in an AWS environment, a test must clear the data it created before the next execution. The reason to clear before instead of after is that this makes debugging easier when a flaky test fails as one can see what state the test was in when it aborted.
  • Integrate into CI pipeline: When using the Cypress junit reporter, CIs such as GitLab can be configured to pick up the results automatically and show them in the pipeline. Also the screenshots Cypress takes upon error should be stored as artefacts of the pipeline run. This makes searching for the cause of errors much faster.
  • Write tests so they can be run anywhere: Ideally, the difference between running a test against a local Docker environment and running it against our AWS test environment is just a change of the Cypress configuration / environment variables.
  • Don’t use intercepts to return fake data: While Cypress’ capability to intercept requests and return predefined data is very convenient, it adds more places that have to be adapted if the underlying API changes.
  • Use Typescript instead of Javascript: for writing tests, it usually doesn’t make that much of a difference in practice. However, when trying to establish a culture of “every engineer writes tests” (as opposed to only dedicated QA engineers), using a more modern language like Typescript increases the chance that engineers will accept tests as part of their ownership.

Things to Avoid

  • Do not use cy.wait for fixed amounts of time: Timing will change depending on where the tests run. One CI runner might be faster, another slower, your local machine completely different. Therefore use of things like “wait for 5 sec” must be avoided. Instead, use cy.intercept(…) to wait for the event that needs to finish before the tests should continue.
  • No side effects: Tests should be able to run in any order or in parallel. If the setup for each test would be prohibitive, have the data created by the test suite (e.g. a single Cypress test file) and then run multiple tests (=it(…)) against it.
  • Increasing Timeouts: Be careful of increasing timeouts. If a long timeout is needed, this usually is an indicator the application/frontend itself is slower than a user expects.
  • Too many Custom Commands: Don’t overdo it. Usually only very few custom commands are actually needed and wrapping some basic stuff into commands makes the code harder to read for people that don’t know what the commands do.
  • Don’t check for specific text: Strings presented to users often change when for example typos are fixed or translations adapted. Define a data-testid on the element that shows the text and rather test for that.
  • Don’t check against URLs: URLs are always subject to change. Instead, simply check for the content that is presented on the page.
  • Don’t use Cypress-if: This is an anti-pattern, as stated also in the Cypress documentation.
  • Don’t assume a certain cookie is set: Unless the test itself sets it, assume a blank browser with empty local storage and no cookies. e.g. the cookie consent form might show up depending on the environment, the test might have to switch to a particular user language, …

Conclusion

End-2-end tests are a powerful tool that is often underutilised because of misconceptions and suboptimal implementation. To employ them successfully in a project, one needs to be careful that certain fundamental principles are adhered to as otherwise they quickly get a bad reputation and will ultimately be rejected by much of the engineering team. Used correctly, they can be fast, provide strong, easy to understand guarantees and require little maintenance. We hope the ideas in this post might inspire you to use them if you haven’t done so so far or re-consider them if you had poor results using them in the past.

We’re happy to announce that the Tech Radar we have been using internally is now publicly accessible! It can be found here.

So what is a Tech Radar? The concept was first popularised by the software consultancy Thoughtworks as a means to share knowledge between their teams working over a wide range of companies and projects. It quickly turned into an industry standard, with many engineers each quarter checking out what new technologies would be recommended or which no longer should be used.

Inside Taxdoo Engineering, we use our tech radar mainly as a tool of discussion and alignment. Any engineer can suggest an update, which naturally leads to discussions on what the collective wisdom is. We see it as important that any engineer can suggest changes, not just certain roles or titles, but of course our architect and lead engineer review the radar periodically to make sure it’s aligned with Taxdoo’s long term strategy as well.

The visualisation that can be seen on our website is based on the open source Zalando Tech Radar Github project with some minor additions to also be able to show our reasoning behind each decision. While we tried to add meaningful descriptions to each point, here are a few high-level trends we have been noticing:

AWS Serverless at Taxdoo

Taxdoo has always been heavily relying on AWS Serverless, in particular API Gateway + Lambda, and that’s a trend we foresee to continue. When we talk to people outside of Taxdoo, we are often met with two questions: Isn’t that quite expensive and why are you not using Kubernetes?

The simple answer is: because AWS Serverless just works for us! Of course, for a lot of the number crunching and heavy lifting we do use Docker + ECS, but the main parts of our applications our users are interacting with are almost completely serverless. This greatly simplifies our infrastructure and makes topics such as security or compliance much easier to handle. As an example, a recent recurring audit performed by one of our external data suppliers went much smoother because we have so few servers in our production environment. Less servers, less security updates and firewall settings to worry about.

Serverless also fits perfectly to the usage patterns our customers have. They need 100% reliability, but we do not see high loads over long stretches of time. Matching our motto “Taxdoo and done”, users jump into the application, are able to quickly perform their tasks, and then jump out again. Due to the nature of our business, we also see usage peaks around monthly filing deadlines as prescribed by the various countries’ tax authorities and a lower usage during the other days of the month, because things simply work. As a result, AWS Lambda + API Gateway is an almost negligible amount of our operational cost, especially comparing it to the storage cost of the large amounts of data we are processing every single month.

Speaking about Kubernetes, it is a great tool. However, we have so far not come across any use case that would warrant the additional complexity over basic API Gateway + Lambda. We are not only avoiding additional costs of operation and training required to properly use Kubernetes but are also completely avoiding a whole class of Kubernetes-specific issues. So while we are often met with a lot of enthusiasm for Kubernetes (for example while talking with potential recruiting candidates), for our use cases, it simply doesn’t add any value.

Increased Usage of Docker for Local Development & Testing Setups

We had been using docker as part of integration tests for a number of years, but often only to simulate individual parts like our product database. We have now reached a tipping point where so many of our services are also available in dockerized form that more and more projects start to build more complex e2e/integration test setups that can be run isolated for each MR or locally. Once a project is at this point, it is easy for it itself to offer a docker image, thus continuing to spread our coverage. We just recently reached the important milestones of being able to run our main user-facing application completely inside docker with only a few dependencies being mocked.

There are of course areas where a docker approach runs into issues. We have faced this in particular when using more AWS-specific features such as AWS AppSync or AWS Step Functions. In the case of App Sync, we had put it on hold for other reasons already and we use Step Functions mainly in our non-user facing parts of the application where we can use different testing approaches. Still, it is important for us to be conscious in which areas Docker will be able to help us and in which an overreliance on it might severely limit us.

AWS Step Functions & Consolidation

Speaking of AWS Step Functions: so far, we have had very good experiences using Step Functions in some of our production workflows and am excited to use it more whenever the matching use cases arise.

In a lot of other areas though, we see a trend of consolidation and simplification rather than one of adding new tools to our belt. We are for example actively pursuing removing the last remnants of our PHP code, not because modern PHP isn’t capable of doing what we need, but because removing it further simplifies our setup. Having one less language involved will make it easier to onboard new engineers to the project, especially for those of our teams that usually don’t work as much with UI and just want something where they can quickly jump in, add a few changes, and get back to their core tasks.

In a similar vein, AWS App Sync turned out to be enticing at first but not worth the added complexity of an additional AWS service in the long run for us. To fulfil our real world use cases, we had to add some custom code and we also ran into issues that were hard to diagnose. We had only used it in the limited context of a single micro service though and thus moving that functionality to our more traditional API approach will be possible during the next iteration of that part of our application.

Want to thrive in tech? It might come as a surprise that hard skills alone are not enough to progress your career as a developer.


At Taxdoo, we believe that nurturing Emotional Intelligence (EI), as well as technical skills, is the key to a healthy, productive environment.

Why care about EI?

The stereotype of a good developer coding alone in a basement surrounded by empty pizza boxes is outdated (was it ever that realistic?!). Modern tech companies thrive when their teams are diverse, communicative and feel comfortable. The former archetype of a developer is narrow, promotes an unhealthy work/life balance, and requires little emotional intelligence or social interaction. The reality of working in tech is that you need to be a people-person, because creating software for humans is about understanding humans and most of all, understanding yourself.

You may be wondering, in such a results-oriented role, where code quality and features deployed are often used as measurements for success, how is it possible to cultivate and demonstrate emotional intelligence?

Worry not! We’ve compiled some practical tips on how you can boost your Emotional Intelligence on the job.

Self-awareness

This one is a big one. And for that reason, it’s an uncomfortable one. As a developer, it’s a good idea to reflect on who you are and how you show up in day-to-day life.

Example:
You write comments on a merge request that is detailed yet succinct. You send a message to the developer let them know that you’ve submitted your feedback.
Silence. They don’t respond to you for a day. You send them an email, just to make sure they know you’ve reviewed their work. In the next stand-up, you directly ask why they haven’t responded. They give a blunt answer about being too busy. You feel irritated by their response- don’t they want to get this work done promptly? You decide to complain to your tech lead about this. They listen to your concerns however seem bemused that you are taking this instance so seriously. You start to feel alienated and disinterested in your role.

It is completely normal to not be fully self-aware. In this instance, it pays to consider the perspective of the person you are working with. With this example, it would help to ask for feedback from your colleague who wrote the MR or your tech lead to understand how your behavior is influencing the environment.
Sure- you are doing your job, reviewing code and communicating efficiently- but the way you do this will impact how other humans react towards you. Could sending a Slack message followed by an email (without a response from the initial message) be perceived as pushy or passive-aggressive? Was your tone neutral, friendly, or aggressive? Was stand-up the best opportunity to address your concern? Could it have come across as accusatory or as if you were ‘throwing that person under the bus’?

Instead of complaining to your tech lead about the lack of communication, this would have been a great opportunity to ask a trusted colleague for feedback. By asking for feedback from a trusted friend or colleague, you hear another person’s perspective and start to train yourself to proactively consider how you can positively impact others and therefore be perceived as a reliable and trustworthy colleague who people love to work with.

Looking for more advice on improving self-awareness? The Harvard Business Review outlines great tips and examples for improving self-awareness as a business leader, all of which can be applied to your role as a developer or tech professional.

Self-regulation

Let’s face it. 2023 was a tough year for the tech industry. Layoffs, instability, reduced resources, and increased workload are all factors that contribute to feeling stressed and overwhelmed. Whilst these conditions are far from optimal, the way we react during these times will have long-lasting effects on our careers, our relationships, and our health.

Self-regulation to the rescue!

Learning to self-regulate is not about repressing feelings or sugarcoating hard times; it’s about feeling those feelings without letting them control your behavior. According to Psychology Today, ‘self-regulation may be behavioral or cognitive, or both’. Put simply, you are either self-regulating behavior or thoughts. Here are some ways you can manage these feelings healthily and productively.

Example:
It’s the last day of the sprint. You’re the most senior developer on your team so you’ve picked up the most important piece of work. And it’s not finished. Nowhere near ready to go to a Quality Assurance Engineer for review. Your Product Manager pops up to your desk after stand-up and is visibly stressed. They start quizzing you about why this valuable feature isn’t ready yet. Why didn’t you give more updates on this sooner? Why didn’t you ask for the deadline to be extended? Don’t you realize how much revenue we might lose if this feature is delayed? The client wanted this a month ago!
Everyone on your team is silent. It isn’t fair that you’re being publicly grilled. You feel yourself clamming up. Then getting really angry.

Deadlines and stress are common in tech. Whilst you can’t control how fairly your colleagues will treat you, you can control how you react to their behavior. In this example, you can start by acknowledging that you feel angry. Why wouldn’t you? Their behavior is unpleasant and unreasonable. Then, take your time to decide how you want to react or if you want to react at all. Saying nothing is also a response.
Regardless of how you decide to react, make time to reflect and journal afterward. Were there particular words or patterns that triggered you? By making a practice of understanding common themes that you find triggering, you will become better at understanding how you can observe those feelings in the moment, process them, and react in a way that is authentic to you yet also appropriate for the environment.

As a developer, how can you do this?


1. To start with, make it easier to stay in control. Got a deadline? Mute any distractions (Slack, put your phone on flight mode) and dedicate time to purely focus on completing the task before the deadline. Also, communicate to your team and other stakeholders what you need to focus on so that they are aware and can support you.


Remove anything that breaks your focus or could cause you to become distracted.

2. Acknowledge your feelings
Feeling worried about your job security? Schedule some time to worry. Write down all of your thoughts and fears. Make a plan and set a date to take action. This plan might include having a frank conversation with your tech lead or HR, it might include updating your CV or looking for a new role. Whatever the action involves, commit to it and after this scheduled time of worrying is up, move on and do something else.

These examples are ways that you can acknowledge negative emotions and react in a way that is practical and measured. This cognitive skill is highly prized and if you want to become a respected leader, self-regulation is something you should try to master.

Motivation

Motivation drives actions. Motivation is often linked to highly productive and resilient individuals. Staying motivated as a developer can be a challenge. For quick ways to regain focus and stay mentally well, you might want to try the Pomodoro technique. This technique splits time into chunks of just under half an hour, which helps you to stay motivated and avoid becoming overwhelmed.
However, this hack won’t solve a deeper lack of drive. Most of us code because we love it, some of us code because it brings a steady paycheck.
Regardless of career choice, it’s important to understand what motivates you intrinsically, regardless of financial compensation, status, or any other external reward. The saying goes that if you find a job you love, you won’t work a day in your life.
If you find a job that matches your personal goals, values, or beliefs, you will find it a lot easier to stay motivated when the going gets tough. As developers, we are so lucky to have an abundance of opportunities available to us, so understanding ourselves will make us better employees and help us to make choices that create a career we love.

Example:

You’re applying for a new role at a company that is a completely different product with a completely different tech-stack, some of which is very new and you’ve never worked with it before. It offers a huge pay rise, a flexible hybrid working environment, private healthcare, your potential new boss is ex-Google, and share options are also available after passing probation. This company invites you to submit a project using a framework you’ve never used. You invest a lot of time in the project and you manage to submit it within the deadline. Success! You are invited for an interview with the hiring manager. They explain that this company has significant technical debt, and is a bit understaffed however the culture is good and senior leadership is understanding and supportive. A few days later, they offered you the role.

From this example, can you discern what the intrinsic and extrinsic motivators for this role are? Extrinsic motivation would be feeling excited about getting paid more, getting better benefits, and enjoying a flexible working policy. All of these factors are important, however, they have little to do with actually building software- they could be offered with almost any computer-based role. Intrinsic motivators would be relishing the thought of learning a new tech stack or hacking away at the tech debt to offer better solutions for the company. Conversely, an understaffed team might be enticing as you’ll not be short of exciting work to do. As long as there is a hiring term plan and supportive leadership, a high workload for a short amount of time can be rewarding.


A question that helps you understand whether your motivation is intrinsic or extrinsic is asking yourself: when was the last time you experienced joy when coding? Regardless of whether this was at work or in your time, an important part of coding is being excited to code and learn. Tech is a fast-paced industry where frameworks, languages, and business needs change rapidly. If you are intrinsically motivated to solve complex problems and keep learning, you will find your career a lot more enjoyable.

Empathy

Empathy is a skill that is becoming ever more sought after. Why? Humans are drawn to empathetic people. Empathy is not the same as sympathy; the latter is often about pitying and therefore distancing yourself from another’s situation. Empathy is about finding the similarities in a human experience and understanding what that person is going through.

Example:
A new front-end project is on the horizon. As your team has a strong suite of front-end development skills, it’s looking likely that the project will be assigned to your team. The assigned Product Manager set up a kickoff meeting. It’s time to gather requirements. In this meeting, do you ask questions about how current users experience the app? Do you ask questions about which framework or component library will be used? Do you enquire about data that outlines pain points for users? Do you ask about how much time and how many developers will be allocated to this project?

These are all great questions when gathering requirements and defining the scope of a project. Yes, as a developer, it is important to lead on how a project will be technically built and implemented. Yes, most revenue-focussed companies require a specific delivery timeline. However, by exercising empathy at the requirements-gathering stage of a project, you put the user’s needs first, which means that the solution delivered will be most valuable. It will also demonstrate that you are open to the wider context of the business and that you care about who uses the tool that you are building.


In addition, communicating authentically is part of leading with empathy. Can emojis help? They might well be able to foster engagement and build happier teams, according to Forbes. In digital communication, emojis make it clear what sort of tone you wish to convey and inject humor into communication that can otherwise be transactional or impersonal. Of course, use them with caution and not in formal settings, however, you may want to consider using emojis to bring more personality and warmth to your everyday communication style.
Remote, hybrid, or in-person, asking your colleagues about themselves and actively listening to their responses also shows that you are empathetic, and care about them as individuals whilst also respecting professional boundaries.
A good way to show empathy is to simply ask your colleagues how they are, check in on them and every time you speak to them, actively listen.

It is a simple tip, but listening and remembering details about your colleagues is a great way to show them that you care and are aware of what they might be going through.

Social skills

An often overlooked soft skill, having great social skills will propel your career forward hugely. Of course, businesses value individuals who deliver results but also those who invest in social capital by building networks with their co-workers. This not only facilitates efficiency but also promotes a healthy, collaborative environment.
And the best thing is… you don’t have to be an extrovert to have great social skills!
There are a host of ways that you can reach out to your teammates or colleagues (Slack, email, video call, or an in-person catch-up), however, the main thing is to communicate with them authentically and if possible, to make time to understand them aside from the topic of work. Be aware that some colleagues may view work as simply that and may not feel comfortable with getting to know other colleagues.

Example:
You’re in a meeting. Your senior, your team lead and the client are also in the meeting. After introducing yourself, you sit quietly and wait for someone to ask you a question. The senior fields most of the difficult questions that the client has, with your team lead backing him up where necessary. It’s an intense meeting and you feel quite overwhelmed by the client’s requirements. With a silent sigh of relief on your part, the meeting comes to an end, and aside from the introduction round, you’ve said nothing. You can get back to finishing your ticket for the day. You want to get yet another piece of work finished, especially as you have a one-to-one meeting with your team lead next week and you’d like to ask for a promotion because you’ve been delivering a lot of good solutions.

If this example is typical of how someone behaves in a collaborative setting, how likely is it that the individual will be promoted? True, being productive and consistently finishing work is part of being a good worker. However, staying in your comfort zone and being silent in meetings means that you lose out on opportunities to show that you are a team player, to showcase your work, and to support your team. Public speaking is daunting and is often not a skill that is taught, however by starting small, for example, to ask project-relevant questions in meetings or proactively offer a response if you know the answer, you can build your confidence and trust with your colleagues as you go. 


You might feel shy to begin with however with practice, you will feel more confident. Start by asking open-ended questions, then ensure to check in regularly and if you feel comfortable, attend work-organised social events.Remember, it’s not a popularity contest, more an opportunity to create a network of supportive individuals that will help you, your team and your organisation achieve success.

Emotional intelligence is becoming increasingly important. The tech industry is growing, and whilst there may still be a standard of technical skills that are deemed crucial for certain roles, it is always good to remember that these can be learned. Developing emotional intelligence can be learned, however it is most effective as a constant mindset.
After reading this, you may wonder where to begin. Self-awareness, self-regulation, motivation, empathy and social skills are all components of emotional intelligence- why not score yourself on each area and pick one to improve on? We all have our unique strengths, so we may as well make the most of them.

Would you like to work in an environment that values emotional intelligence as well as technical challenges and interesting projects? We’re hiring! Check out current vacancies here or learn more about Taxdoo culture.

Stay social and follow Taxdoo Engineering on X.

AWS Lambda is a powerful service that provides a hassle-free way to run our code in the cloud without needing to concern ourselves with the underlying infrastructure.
At Taxdoo, we rely on Lambda for a vast array of services, as it streamlines our development process and ensures our focus remains on delivering innovative solutions.

However, while Lambda is an ideal choice for many use cases, it’s not always the perfect fit. There are times when a more flexible and customizable approach is necessary, such as when dealing with long-running tasks or more complex workloads. In these situations, containerized solution shines as a robust and scalable container orchestration service that can accommodate a wide range of needs. By moving from Lambda to the containerized solution, we were able to achieve greater control and flexibility while maintaining the efficiency and convenience we’ve come to expect from AWS services.

Recently, we encountered a well-known limit of Lambda functions, which is why they have a 15-minute execution limit. Over the years, as Taxdoo has grown so has the amount of clients we need to prepare invoices for. This task used to take a few minutes to complete, but not too long ago, we quickly encroached on the 15-minute time limit. 

We needed to move our workload to an AWS service which didn’t impose a 15-minute execution time limit. We needed something that could be set up fast and required very little maintenance.

Furthermore, we considered moving the workload to an EC2 instance but quickly dismissed the idea as the workload only runs a few times monthly and also runs for around a maximum of 30 minutes. While we could have spawned an instance as needed, doing so would have burdened our team unnecessarily. Furthermore, we preferred to continue using CloudWatch rules for scheduling and invoking our workloads, which can be complex to manage with EC2.

Moving from AWS Lambda to a containerized solution, we chose to use AWS ECS, a fully managed container orchestration service that simplifies the deployment, management, and scaling of Docker containers on the AWS cloud using EC2 instances. Here are some of the key benefits of using ECS over EKS that we found:

  1. Simplified management: With ECS, we were able to simplify the management of our containerized applications. ECS is easier to set up and manage compared to EKS, especially for those who are new to Kubernetes. EKS requires more investment from a technical perspective and base cost perspective.
  2. Better integration with AWS services: As an AWS customer, we found that ECS integrates more seamlessly with other AWS services like CloudWatch or CloudFormation This made it easy for us to build, test, and deploy our containerized applications using familiar AWS tools.
  3. Focus on our application code: With ECS, we were able to focus on writing our business logic instead of worrying about the underlying infrastructure. ECS provides a managed Docker environment for our containers, which meant we didn’t need to spend time setting up and maintaining our own cluster inside AWS.
  4. Cost-effective solution: We found ECS to be a cost-effective solution for our container orchestration needs. We didn’t need the same level of infrastructure or any expertise in Kubernetes as it would be needed for EKS, which helped to keep our costs down.
  5. Highly scalable: ECS is highly scalable, allowing us to quickly scale up or down based on the demand of our application. This ensured that we could deliver the best performance and experience to our customers.

Overall, we found ECS to be a great choice for our company when moving from AWS Lambda to a containerized solution. It simplified our management, integrated better with AWS services, allowed us to focus on our application code, and provided a cost-effective and highly scalable solution.

ECS allows us to run our tasks on EC2 instances which ECS provisions for us as and when required, but it also allows us to run our workloads using Fargate. Fargate allows us to run our ECS tasks and services on AWS-managed servers, similar to Lambda. 

When comparing an ECS Fargate task to Lambda, it’s important to note that the primary distinction is that an ECS task requires a Docker image, whereas Lambda can run a variety of programming languages natively (as well as Docker images, if desired).

Initially, our service was intended to operate within a Java lambda function. In order to transition to a containerized solution, our first step was to create a Docker image capable of running our task. To achieve this, we made only a minor modification to our task code by implementing a main method that could be linked to a Docker image. Fortunately, we found that a simple and straightforward vanilla Java Docker image in which we simply referenced our output jar file was more than sufficient for our needs.

				
					FROM openjdk:19-jre-slim
COPY project/path/code.jar /docker/path/code.jar

ENTRYPOINT ["java","-jar","/docker/path/code.jar"]
				
			

In order to run ECS tasks within an ECS cluster, we need to create a task definition. We did this using AWS CDK. One particularly useful method offered by the CDK was `ecs.ContainerImage.fromAsset` which did three things for us:

Build the docker image for us, push it to a managed registry for us, and link it to the task definition we’re defining.

				
					const createEcsFargateTask = () => {
  const taskDefinition = new ecs.FargateTaskDefinition(
    this,
    `yourTaskName`,
    {
      cpu: ...,
      memoryLimitMiB: ...,
      taskRole: taskRole, // role which is executing the java code
      executionRole: executionRole, // role which is triggering the ecs fargate task itself
      runtimePlatform: {
        // define runtime here
      },
				
			
				
					 }
  );

  taskDefinition.addContainer(`yourContainerInstanceName`, {
    containerName: 'yourContainerName',
    // import docker image here for the container
    image: ecs.ContainerImage.fromAsset('..', {
      exclude: [
        // good for excluding unused modules
      ],
    }),
    environment: {
      // set environment variables here
    },
  });

  return taskDefinition;
} 
				
			

Code we added inside our CDK configuration

And just like that, with a simple update to the target for our CloudWatch rule, we bid adieu to Lambda and welcomed our application into the world of ECS. Migration complete!

Software engineering is all about coding. Right?

Wrong. Well, just a little bit at least. Software engineering is way more than writing lines of code. Problem-solving, understanding complex business systems, specifying the scope of project implementation, defining contracts between clients and customers, coordination (for resource allocation, deployments, interlinked pieces of work), prioritizing, communication between colleagues or with Product Managers… the list goes on.

In fact, for many developers, coding is the easy part. You can practice this craft to perfection. Got a bug? Fix it. Data table locked and clients can’t log in? Declare an incident and fix it. Deployment of a new release failed? Fix it.
We, developers, are innate fixers. Logically minded, we love building solutions that help someone in some way. Which in itself is an innately empathetic trait.

Yet, when deadlines are tight, or the team is stretched too thin, or even when everything is going brilliantly, we can sometimes forget to work on the long list of other skills we need to do our job which doesn’t involve code. This is where having good Emotional Intelligence is incredibly valuable. Not only for yourself but a survey of hiring managers confirmed that 75% of hiring managers valued high EI over high IQ.

Firstly, what is Emotional Intelligence?  As April Wensel said in her talk at PyTennessee in 2018, it’s not about sugarcoating difficult conversations or being an extrovert. Emotional Intelligence is about not being ruled by your emotions. 

American psychologist, Daniel Goleman, popularised emotional intelligence and defined five key principles that underpin EI; self-awareness, self-regulation, motivation, empathy, and social skills.

How can we apply this as software engineers? We’ve broken down the five EI pillars into simple definitions and, based on extensive research and lived experiences as developers, we’ve collected some examples and tips that you can use to boost your Emotional Intelligence.  

Self-awareness

Self-awareness is being able to understand your emotions and how your reactions to your emotions affect others. As a developer, it’s good to spend time reflecting on your intrinsic strengths, weaknesses, environments that you have a natural preference for, and potential situations that can trigger you. A good example of having good self-awareness is knowing how much you can do.

It happens often in teams of developers that a sprint finishes with initially committed user stories unfinished. Self-awareness enables the team to take on and commit to finishing a realistic amount of work based on the complexity and skill set of the team. An example of this may be considering whether, even on a good day, could you complete an 8-point piece of work within a working day. It’s unrealistic to commit to this if you know you would struggle or have never completed a piece of work like this in that time.

How can self-awareness help you as a developer? In a nutshell, it will help you to recognize and mitigate the impact of stress by understanding your skills and limitations accurately.
From tight deadlines to receiving constructive feedback in 1-1s to tackling criticism during code reviews to simply working in the fast-paced world of software development, the ability to recognize when you are experiencing stress will help you to limit the impact it has on your health and develop healthy coping mechanisms.

To boost your self-awareness, ask yourself the following questions and journal your answers.

Complete this exercise once a month and review your answers from the previous month.

Do you notice any differences?

Self-regulation

Self-regulation is the ability to manage your own impulses, energy, and moods and to think before you act. This is particularly useful when you apply this to managing your capacity at work and how you react in a professional environment. We probably all know the impulsiveness that comes with working behind a computer screen; less actual face-to-face time, brisk deadlines and the sometimes unavoidable insularity of coding can mean that you can get stuck in your head and forget how to regulate yourself when it comes to interacting with other human beings.

For example, let’s say we’re a junior developer who has just started learning Java. They see a high priority ticket in the sprint, weighted at around 8 points. We all know the feeling. We want to impress and please our team. But we don’t have the skills needed to complete the work, especially in a specific timeframe. Self-regulation, in this case, will mean that you decline to pick up the ticket but maybe offer to pair or review the work of the more senior developer who picks it up. Sure, you’re turning down an opportunity to shine but you’re also not being controlled by your pride and accepting work which will be stressful to complete well. Instead, self-regulation helps you to make a decision that is realistic and benefits the team, as well as the business you work for.

This might present as getting irritated at correcting a colleague’s code format for the umpteenth time (despite also asking them to install a linting extension in VS Code) and writing a slightly too direct comment on their latest merge request. With a few grumpy exclamation marks to boot.

When you are unable to self-regulate, you react in ways that are driven by your emotions, which isn’t always appropriate in a professional setting. Outbursts like the one above are likely to make your colleague more hesitant to ask for your help when they next have a merge request to review, for fear of getting the same reaction. Over time, reactions like this lead to a less collaborative environment. Had you been able to self-regulate and calmly explain why the changes needed, your colleague would’ve made the adjustments and would trust you as a source for support in future.

Motivation

What motivates you? Many of us working in tech are lucky to be well paid, have interesting projects, a work-life balance, and have supportive colleagues (or, at least a mixture of some of these).
But does any of this motivate you intrinsically

It may well be that none of these motivate you, which is OK. Understanding what drives you will help you write and keep writing the best code you’ve ever written. If the going gets tough, you’ll already know how to tap into your innate ambition and stay focused and positive. You’ll also be more likely to take the initiative to take charge of your own career and create your own opportunities, as opposed to following the path set out by your employer or what is expected of you by others.

This is not about promoting ‘hustle culture’, but more to highlight the importance of knowing what keeps you going and cultivating a proactive mindset which means you are ready and motivated when opportunities that align with your goals present themselves.

Empathy

Empathy is the awareness of the feelings, needs, and concerns of others. Being aware of how others experience the world means that as a developer, you are better able to communicate and build relationships with lots of different people.

Take our example earlier of writing an irritated comment in response to a mistake on a merge request. When exercising empathy, you may consider whether your colleague has made a mistake because they are stressed or whether code quality standards are communicated clearly across the department. You would then share your feedback on the merge request calmly and perhaps check in with this colleague.

To be clear, empathy is not about lowering your standards or cutting people too much slack. It’s about considering others and considering how your actions may impact them. A good example of exercising empathy is setting easy-to-understand variable names. Underpinning a key principle of clean code, setting variable names which are easy to search, intentionally named, and describing the logic of the variable makes life much easier for the next developer who comes along and has to work out what the code does. This contributes to how easy a system is to maintain or re-architect. Ever designed an API? Developers are your users, so develop and publish an API and related resources that are simple and if possible, include a feedback option so your users can ask questions. At Taxdoo, we offer Developer Support for our API via Reddit, so that developers can read discussions and share knowledge.

Ever heard of Ubiquitous Language? Eric Evans coined this term in Domain Driven Design to describe the practice of establishing a language between users and developers, based on the Domain Model. By using empathy to develop Ubiquitous Language, we then have a framework of communication between developers and business domain experts that makes the feedback for testing or developing a product clearer and much more impactful.

Being aware of how others feel is also key if you aim to become a leader. People gravitate towards those who can unite a diverse group of people and bring out the best in them. Highly empathetic people are also more likely to be able to acknowledge and successfully navigate political situations at work in a way that delivers results, for example, securing promotion for a direct report or receiving more resources to better complete a project, without compromising their integrity or neglecting the development of their team.

Social Skills

Social skills are just for social butterflies and extroverts? Of course not! Even for the most shy or introverted, social skills can be developed in an authentic way so that you can connect and work with others. Social skills cover a breadth of qualities; the ability to communicate (think honestly and clearly, rather than chatty and convoluted), the ability to build trust and bonds with colleagues and most powerfully, to yield influence based on the high regard that your colleagues hold you in.

Let’s uncover another myth: having good social skills is not about being popular or striving to make everyone like you. Social skills are about presenting yourself authentically, being an uncomplicated person to work with, and being someone who can mentor, manage and resolve disagreements in a way that builds trust, rather than erodes it. In many organizations, people who possess brilliant social skills can also use this to spark change in a positive way. Does the company want to become agile? Advocates will be selected based on their sphere of influence to spearhead a cultural shift and ensure that the adoption remains high.

At the end of the day, we know that Emotional Intelligence is often overlooked as a ‘mere soft skill’. We like to measure success with cold, hard metrics. So why care about Emotional Intelligence, especially in a logic-driven field such as software engineering? Because a high IQ alone is not enough to achieve success.

Much as we are expected to have multiple skills and areas of knowledge as an engineer, to work sustainably and be more impactful, we need to address how we behave, react, and present ourselves at work. The great thing about EI is that it puts the focus back onto personal growth, emotionally and mentally, as opposed to professional growth, which focuses on acquiring new skills and experiences to remain competitive. 

Combining both and suddenly, we, as engineers and tech professionals, can sustainably deliver value as well as grow as individuals. 

Not sure how to approach this subject with your team? Share this article with trusted colleagues on chat messenger, ask them to read it, and see what they think. Perhaps it will spark a healthy debate on what processes a team or department could work on to promote a culture of emotional intelligence.

Or if a problem keeps arising around code reviews or difficult-to-understand documentation, use this article as a conversation starting point for addressing these issues in retros or post-mortems. It is good to note that in some organizations which are purely results driven or where mental health or wellness at work is not prioritized, that the topic of Emotional Intelligence may be viewed negatively or could cause someone to take offense. Not everyone thinks the same or is as progressive as you’d hope them to be, so consider before you decide to raise this topic in your workplace.

We hope that you found this article. Please feel free to share your feedback or experiences with us!

Maria, Engineering Manager at Taxdoo, delivered a presentation on Emotional Intelligence in Software Engineering at the FemTech Conference in 2023. Covering the definition of Emotional Intelligence, management psychology, the professional benefits of high Emotional Intelligence to practical tips on how to improve your own emotional intelligence, her talk was a deep dive into EI that aims to provide practical ways to improve your own EI whilst supporting your team in an EI-positive environment.

What is Domain-Driven Design and why is it helpful?

Domain Driven Design (DDD) is an approach to software development, made popular by Eric Evans’ book, Domain Driven Design, published in 2003. It still receives a lot of attention today and is a highly respected tool in a lot of architects’ toolboxes. 

Why? Quite simply, DDD is about guiding engineers to focus on the business domain (e.g. the terms, rules, and business goals that business experts apply to a given problem), rather than technical concepts or frameworks, and to collaborate very closely with domain experts to find a shared language and model for the domain.

Over recent years, DDD has proved incredibly useful where software businesses scaled fast and developed a need for vertical cuts through their products, teams, and codebase. For example, the rise of microservices requires tech teams to define modular boundaries and assert which logic belongs to which service. DDD is also helpful in creating a product that is aligned with business priorities with fewer iterations, as it focuses on closing the gap between the code and the reality of the business needs.

At Taxdoo, we focus on the domains of Taxes & Accounting. We tend to deal with complex software where the limits of our understanding can soon become limits of our products and codebase. Luckily Taxdoo offers us to spend 10% of our time as ‘innovation time’ where we are encouraged to learn and experiment.

Making use of that, a few of us engineers and domain experts founded a book club, where we spend our innovation time reading and sharing what we’ve learned together.

There’s no such thing as too much collaboration.

With knowledge sharing, it can be tempting to have only software engineers at the table. We’re learning about a coding concept, so why involve other parties?

Having domain experts in the book club prevents engineers from becoming too technical or jumping into ivory tower discussions. 

At Taxdoo, we involve domain experts, called ‘Regulatory Analysts’, in our book club reviews and also our scrum ceremonies.

The result? We are using and constantly optimizing a shared language and mutual understanding about the rules and terms of our business that we use in our daily work and can be applied in our daily work. A huge bonus of involving domain experts so closely is that user stories are written by a domain expert, which reduces ambiguity and deviation. It took some time before both parties learned the terms used by the other, however, it has resulted in greater transparency and clearer requirements.

Business Process Model notation (BPMN): where analysis and implementation go hand in hand

Another interesting learning is that DDD discourages a separation between analysis and implementation.

Often, engineers collaborate with domain experts only until they think they understand the problem. Then they go and write the code, so that the implementation is hidden in the code and invisible to the domain experts. This then necessitates a step of translation, done by the developer, that often unconsciously changes domain logic or language. This drift in precision causes technical debt or bugs as the project progresses.

In Taxdoo, we use decision trees for our tax logic, and in the past, these trees were provided as diagrams by domain experts and then translated into code by us engineers. This approach was fine, however, we still weren’t quite accurate enough in communicating and understanding our requirements.

Last year we migrated this logic to BPMN, which is an industry-standard for business process modeling. The great thing about BPMN is that it combines the diagram and the implementation. This is done through an underlying XML layer. We can now collaborate by visually creating a diagram together and do not need an additional implementation that could impose the risk of misunderstandings or drift in language.

There’s still a long way to go, however moving to BPMN was a great start to close the gap between analysis and implementation.

Value Objects: capturing values in a transient state

DDD is a large concept that can be adopted on all levels of a company. That being said, not all companies have the capacity for this and in any case, it makes sense to experiment with concepts before applying them on a large scale. One application of the suggested code patterns that worked very well for us was the introduction of Value Objects.

In DDD, Value Objects represent values in a transient state, usually bound to one operation. They are distinct from entities, which are longer-living objects that have their own lifecycle and identity in the given context. In contrast, value objects don’t need any kind of identity, because two instances with the same values could be used interchangeably. 

Here’s an example where we introduced such an object and how it helped us:

VAT IDs

The VAT identification number is assigned to a company by the financial authorities and used for value-added tax (VAT) purposes.

In our world of B2B taxes, the VAT ID is a type of value that we need to process often. When a customer provides us with a VAT ID through a form or support requests, we usually receive it as a simple String. A first improvement to the object (to make it a more powerful part of the domain model) would be to restructure it in a way so that it yields more information about its purpose.

While this refactoring is not really providing a lot of additional logic, it already helps to fulfill the claim of DDD refactoring: “Refactoring towards a deeper insight of the domain”. By making this value a dedicated class and destructuring the characters into fields, the code becomes clearer about the contents of the VAT ID String, which is especially useful if you’ve never seen a VAT ID before.
We could now also add validation to make the object even more helpful.

For Taxdoo’s use case, there’s a little more to it: a VatId country code is usually the IsoCountry code of the country where the company has registered. However there are some edge cases. In Greece, the country code for VatIDs is “EL” instead of “GR” and Northern Ireland also has its own country code of “XI” even though its usual IsoCountry code would still be “GB” for Great Britain. 

To encapsulate that domain knowledge as part of our implementation, we can add a dedicated Getter that would translate the encoded country code into an IsoCountry:

By combining this domain knowledge we can provide an even more reliable parsing and validation. We know that the country code must either be castable to an IsoCountry or match one of the special cases mentioned above. If the provided country code prefix in the input String does not match these conditions, we know that the provided VAT ID is malformed:

In summary

Let’s recap: initially, we had a simple value, stored in a String. In the domain of tax evaluation, that value is often processed, parsed, and interpreted. We need to check for the validity of the VatId, we need to derive information and we need to send it across the network.

By making it a dedicated class, we strengthen our understanding of what a VatId is. If a new developer comes into our Taxes & Filings team and sees this code, the code represents the domain model that is useful when applied to our context. A lot of these principles could simply be regarded as “good OOP” or best practice, however in this case it helps us to understand the domain and share the attributes and capabilities of our models with the domain experts. 

In a real-world use case, there’s much more knowledge that can be contained in a VAT ID model inside of the context of taxes: Is the VAT ID valid? When was it registered? Note that depending on the context, the VAT ID could receive an identity with its own lifecycle (it’s even called ID!). In such a case, it would not be a value object anymore but would become an Entity, because we would store it with an identifier, so that we can track its lifecycle and add relationships to other objects, for example, a “Company” object.
Thinking about your objects and if they need a lifecycle in your domain will help you to obtain a deeper insight into your domain. This is a great tool from the DDD toolbox.

Layered Architecture

Our final example: the isolation of a domain layer in the code base. While we applied this as part of our interpretation of Clean Architecture, the isolation of a domain layer is part of the layered architecture that is suggested by the book.
We did this by creating a dedicated package for the domain logic and extensive use of Java Interfaces whenever we wrote code that was related to persistence or other infrastructural tasks. The domain package contains the long-living domain logic (e.g. applying the tax evaluation), while database transactions, API clients, or serializers are hidden behind interfaces with their implementations in a separate data package. The resulting package structure looks like this:

This separation allows us to make changes to the infrastructure (e.g. moving from RDS to DynamoDB) without touching the domain logic since we only need to provide another implementation of the respective interface. At the same time, we can also test our domain logic (which is the most important one to test) without worrying about the setup of a database or external service. It’s also easy to check or lint: No class in the domain package must ever contain an import from the data layer.

4 Tips for Getting Started with DDD in your Organisation

  • If you want to promote DDD or learn more about it in your company, involve domain experts from day one. They will prevent you from drifting into technicalities and you will collectively learn a shared language to discuss your daily collaboration.
  • Getting started with DDD works especially well when you work with serverless architecture (like Taxdoo) since architectural changes can be done faster compared to on-premise (which means that servers would be hosted physically by the software company itself, requiring time and financial resources to maintain their own infrastructure and server hardware) and the architecture is divided into smaller components. You’ll also spend less time on maintenance of non-domain-logic components since most of that is handled by the cloud provider.
  • Leave the larger and more complex patterns (for example Aggregates and Factories) for later and concentrate on the core idea. You don’t necessarily need to rewrite all code, to start your shift towards a more vivid domain model, but you can do that e.g. through the introduction of Value Objects.
  • If you haven’t done that, you can also start by isolating a domain layer first. Once that is done, you can apply more of the DDD patterns inside that layer, if it seems reasonable for you.

Good starting points for further information would be the “Blue Book” or the summary in the form of the DDD reference, both by Eric Evans. If you prefer to watch conference talks, I can also recommend checking out the DDD Europe Youtube Channel which contains a lot of talks around DDD concepts or their (technical) implementations.

Do you have experience with DDD? What were your takeaways from it? If you are new or are leveling up your DDD knowledge, we hope that you found this article helpful. Please feel free to re-share this post on social media or amongst your connections.

Finally, this blog post would not have been possible without the help of Paul, Maria, Alex, and Kevin. Thank you for the book club sessions that we had, and for your thoughts and inspiration!

In Taxdoo you’ll find the room and other motivated colleagues to invest time into new concepts and experiments. Join Taxdoo if you would like to participate!

Moving to management is fun when you know what it takes

It’s not easy being an Engineering Manager and it takes more than just a good work ethic. The first step in becoming a good leader is learning about yourself as an individual. Self-awareness and self-reflection are key if you want to be an empathetic and confident leader. Once you know what it means to be you, it will be easier for your direct reports and colleagues to figure out how they relate their own strengths and weaknesses with yours. They must be able to see how their work contributes to the whole and feel valued as members of a team.

What makes a good engineering manager at Taxdoo? With our Taxdoo values and culture in mind, here are some tips on becoming and excelling as an engineering manager. 

Motivation

One of the most important things you can do to become an engineering manager is to keep yourself motivated. And understand what motivates you. Motivation is a mindset that keeps you one step ahead of your competition and allows you to achieve more than what would seem initially possible. 

In order for motivation to have an effect, the first thing to do is to make sure that there is a clear goal or set of goals that need to be accomplished. From there on, it’s easy. Make a plan with small actionable steps that are part of the overall goal. Once something gets accomplished, ticking it off marks an incremental step toward completing your goal. Not only does it feel good to work in this way, but it also strengthens the resolve of yourself and others as progress is tangible and constant.

Empowering Others to do their Best Work

A good engineering manager knows that empowering their team is their primary role.

Good leaders celebrate and hone the unique capabilities and personalities of their direct reports and strive to support each individual to grow whilst delivering business value. By trusting their team to self-organize with day-to-day operational tasks (such as attending stand-up or organizing code reviews), an engineering manager can then focus on coaching engineers and inspiring them to do their best work by gradually assigning them more significant pieces of work which challenge them and with guidance, push them out of their comfort zone. 

Feedback is key

Constructive feedback is key to the success of your team. It’s about giving your team members the information and guidance they need so they can succeed, but also being humble enough to accept feedback from your team or your own manager. At Taxdoo, our culture fosters frequent feedback and this is one of the things our future engineering managers need to encourage as a constant.

To us, feedback is the cornerstone to creating a culture where mistakes are embraced and a growth mindset is celebrated. And encouraging our engineers to be brave enough to take action rather than avoid risk. It means setting up systems for continuous improvement and feedback loops between leadership and employees so that everyone knows what’s expected of them in each role on the team, or across teams. If you’d like to find out how we embed this from day one, check out our Onboarding blog post.

Celebrate mistakes

It’s important to remember that mistakes are encouraged when you’re an engineering manager. Think of it as an opportunity to learn and grow. No one’s perfect, so why should you or anyone else be punished for it?

In an engineering setting, we all know that the business of shipping code can be a risky business. Deployment failed? Major outage? Stay calm and fix it. Then assemble your team for a blameless incident post-mortem. Take the lead and share the results with the engineering department and the rest of the business. No one is more stressed than your engineers when something goes wrong; it’s your role as a manager to steady the ship, investigate what happened, support the solution, and show solidarity with your team in front of the entire business. This fosters psychological safety, which will unite your team when times get tough.

At the end of the day, we work in tech, not the emergency room.

Communication solves everything

Communication is so important when building a team. Something that people often miss though is that communication isn’t just about talking; it’s also about listening.

Communication in the workplace is essential because it facilitates collaboration in an environment that brings a diverse range of people together and helps them to harness their unique abilities to achieve success on projects and tasks that are important to the company. Collaboration is something we cherish deeply at Taxdoo. We believe that it allows everyone to simultaneously experience how their work impacts other teams, and how other teams’ work impacts their own. This builds awareness and makes the path to success smoother than ever.

Everyday is a school day

It’s important to keep learning, especially in a fast-paced environment like ours. You can learn from your team, and they can learn from you. Everyone has different strengths and weaknesses, so it’s best to understand each other before jumping into a critical piece of work.

For example one person might be great at writing code but not so great at understanding what their teammates are talking about; another person may have a very strong background in programming but not so much experience with business management.

We could go on and on, but the point here is that everyone has something unique to offer—and those differences should be valued as much as their similarities!

Learning is a lifelong process. You can’t stop learning and you never have to stop learning. The best thing about it is that it’s not just about reading, watching, or listening; it’s also about asking questions.

Learning isn’t something that happens once in your career—it’s a process of trial and error. Try new things and fail at them until you figure out why they didn’t work for you before giving up. Alternatively, you can try again with another approach or strategy to see if that makes sense for you as an individual, team member, and employee at Taxdoo.

At the end of the day, what you do matters more than what you say

As a new leader, you’ll face challenges that will push your boundaries and stretch your skills. You’ll have to learn new things and put them into practice, including how to empower effectively, motivate others, and communicate with the people on your team. Becoming a good leader is hard work.

If you’re interested in having a role that enables you to make an impact and grow as an individual as well as part of an organization, there’s no better time than now.

While becoming an engineering manager isn’t always easy, it’s definitely rewarding. The benefits of leadership are endless, and you’ll be glad you took this step when you see how much your team grows.

This article is written by the engineering managers at Taxdoo. 

André Kowalewski, Katarina Lang, Zeynal Zeynalov, Maria Canero, Alexander Klein and Gabriel Guimaraes.

Hi, I’m Hendrik, a JavaScript enthusiast and Engineering Manager at Taxdoo. Today I wanted to take you on a journey of exploring what technical debt is and how we deal with it here at Taxdoo.

Working with Engineers you probably heard the term “Technical Debt“. But what is this “debt” Engineers keep referring to? Just an excuse to push features further out? Join us on a journey to understand what Technical Debt has to do with doing your dishes and paying back your student loans.

What is debt?

> Something that is owed

In its most basic form, Debt is simply something that is owed to another. The most common debts we owe are:

– Money 💸, that we owe to a bank or student loan.

– A favor 🤝, that I might owe to a friend who helped us move apartments. 

This is what we most often think about when the word “Debt” is mentioned. But there is another kind of Debt that we all face in our daily lives. I like to call it “Dishes Debt“.

Why doing your dishes is important

Most of us use our kitchens every day. We go in, prepare our food, and then leave. One might say:

> Your kitchen provides value to our lives by allowing us to cook with all the appliances we need.

However, this ‘value-creation process of cooking food always leaves behind dishes that we have to wash before more food can be prepared. As a fan of one-pot meals myself, I know my big pot of pasta requires washing right after I finish it.

This is how I came up with the term dishes debt. Dishes debt is exactly that moment in time when I realize I have to do my dishes first, and only then can I allow the kitchen to deliver its core value of allowing me to cook my food.

> Occasionally the value-creating process of cooking produces Dishes Debt that has to be paid down before more value (food) can be created.

We find the same kind of principle at work in Engineering. We call it technical debt.

Technical Debt

In Engineering we as Engineers:

> Provide value to our customers by coding features for our products.

As engineers, it is our responsibility (and passion!) to provide value to our customers by coding the right features for the products we are working on. Similarly to how the kitchen provides value when it comes to food preparation, we engineer products that make our users’ workflow a pleasant and easier-than-ever experience. Of course, as with every process, engineering also leaves behind some byproducts that need to be taken care of before we can create more value.

> Occasionally the value-creating process of coding produces debt that has to be paid down before more value (features) can be created.

Remember the one-pot pasta dish I was talking about? Well, in engineering terms, this is her again.

That being said, the point where we have to do some coding before implementing new features, we call Technical Debt. The only difference here is that as your dishes pile up next to your sink, you can see them, count them, and make a plan of action. Technical debt, on the other hand, is not as tangible, which is why it takes some extra effort from our side to pay it back.

Before we continue to see how we can implement the best strategies for dealing with technical debt, let’s first learn what types of it exist out there.

Types of Technical Debt

At Taxdoo, we divide technical debt into three categories: deliberate, architectural, and entropy. Let’s dissect each.

Deliberate Technical Debt

Deliberate technical debt is one we take on knowingly. We either see it coming intuitively, or we’ve done something similar in the past where debt arose and we can just feel it in our guts. A sentence you hear us say most when deliberate technical debt is involved is “We need to deliver this now”. For the sake of making a deadline, we then cut corners.

> We know there are problems but we accept them to deliver something on time.

These problems we accept or corners we cut come in different shapes. They include “hardcoding” connections, maybe for now you can only filter a table of offices by a hardcoded list of cities while we know that in the future that list of possible filters should come from the list of all offices.

Architectural Technical Debt

In engineering, managing expectations is no easy task. Even more difficult is when we have to accommodate them. This is when architectural technical debt steps are the work we need to do to accommodate these ever-changing expectations.

> The system was not built to support this.

Whether we didn’t know about an important use case before commencing work, our original assumptions were wrong, or our company pivoted, all of a sudden our work becomes more difficult. Now, with all the other things we need to do, we also have to do some refactoring. Often we find ourselves asking the question: What do we need to change in the way we support the current use case to support the new use case?

This type of Technical Debt often arises in the conflict of creating a “solves all problems” vs “solves exactly our problem” solution. A highly-adaptable product might take a bit more time now but pays back with its flexibility later. Something more targeted is faster to accomplish now but will require a lot more work to become adaptable later.

Entropy Technical Debt

Entropy is the fact that:

> Over time the quality of everything you don’t consciously maintain deteriorates.

So many things add entropy to the work of engineers that it’s difficult to even start listing. New team members cannot know the full history of our code, for example, and will naturally work differently. Architecture and requirements change, and sometimes already utilized technology becomes outdated. It’s part of the game, and entropy technical debt is at exactly this intersection of time and relevance.

Washing the dirty dishes

Now that we can understand the different types of technical debts, it’s time to learn how to tackle them. We will do this by once again looking at the dishes debt and how we often overcome it.

Some of us (and I might be included in that) clean just what they need. Like my pot for porridge that I often wash in the morning to make today’s batch.

A trained chef-turned-programmer I used to work with was quite passionate about this one. He taught me that no professional kitchen could run if it didn’t take care of the dishes while cooking was still in progress.




Habits are great, they become involuntary actions that we don’t think about, we just do. And doing dishes every day makes sure we clean small batches that are easy to handle, enabling us to use all our dishes every day.

Sometimes there is nothing else you can do. For days you didn’t pay down your dishes debt and now you can’t see the countertop. You’ve got to put in that hour of effort and do everything now.

Maybe you can even find a way to not have as much dishes debt. Ordering take-out every day could be an option but it produces a lot of waste and is much more expensive. An innovation could be a dishwasher: it’s a bigger, one-time investment but now you don’t have to do the washing anymore, and it can handle a lot in one go.

Technical Debt strategies

The best part is that we can translate our dishes’ debt strategies to pay down our technical ones. Let’s change up a few words together, shall we?

Picture it: there is a new feature coming in but some technical debt is holding us back. No other choice but to fix that first. Only if we had fixed it on time… Now our system wouldn’t be so hard to maintain, estimates wouldn’t have blown up more, and Software Engineers would be at least a tad happier. What a dream.

In Software Engineering we often employ something we call code reviews. In a Code Review, peers check each other’s work and give ideas on what could be improved. Apart from being peer-to-peer learning opportunities, code reviews are also a natural place to catch Technical Debt before it makes its way into our main codebase.

Another great example of doing dishes while you code is often referred to as “scout mentality” – always leave a piece of code you touch cleaner than it was before. To reflect this, some teams always add a fixed percentage of “scoute time” to their estimates.

We at Taxdoo allocate 10% of our time towards technical innovation and learning. This time-budget is up to Engineers to spend.

And for the other 90% of our time, we make sure to prioritize both feature improvements and technical work.

Sometimes you either need to fix your technical debt now because your systems are failing, or there is no feature pressure right now, so your team can take their time to perfect the technical implementation.

For us this sometimes takes the form of taking a whole sprint just dedicated to fixing and improving things.

A great way out of technical debt is to solve the old problem in a new way. If updating the system always takes longer, maybe you can build a configurable system that can get adopted faster and erase the existing technical debt, thus making Engineers happy.

We drive innovation both as part of feature work and in the 10% innovation and learning time that our Engineers get. It makes me extremely happy to say that it has resulted in amazing improvements!




Preventing technical debt

Although we apply the above-mentioned methods to reduce our technical debt, there still are some residues. There probably always will be. This doesn’t mean that we still can’t take more measures to prevent some of this debt from arising, though.

The right tools can already catch some of the debt in the act. Using TypeScript over JavaScript, for example, will help you detect some of those runtime errors during the stages of development. Observability will make you aware of problems while they are still easy to fix. What a relief!

Realizing that debt is something we are likely to incur enables us to consciously take on debt where it helps us to move the business forward and turn it into a part of our prioritization.

Final thoughts

Writing code is a complex problem. Software Engineers constantly need to decide on trade-offs between “thinking about everything up front” – so that we will need no adoptions later – and “building what is needed right now” – so that we are fast. Both are right at times, largely depending on how easy the system is to change later.

When considering technical debt in the context of planning time for implementation we have only two options: either to invest time in preventing debt to arise, or invest time later to pay down the debt created. In either scenario, we need to look at our priorities and make sure we are making an informed decision.

But, and it’s a big but, if you ask me, it’s always worth investing in debt prevention whilst paying down your current debt. Doing this makes sure that your company can react to sudden market changes and not be held back by unfinished business. And we all know it: in a sea full of fish, being quick to react is the most important factor that sets you apart from the rest.

Working in tech is awesome, especially as a software developer.
The opportunity to build amazing products, being paid to learn and working with very smart colleagues are just some of the reasons why this profession is so rewarding.

However, software engineering isn’t always a walk in the park. Tight deadlines, adjusting to remote work, fixing major outages and generally, the velocity of the tech industry are some factors that can cause you stress as a developer. Therefore, it’s important to look after yourself and create simple, daily rituals that support a healthy working routine.

Ahead of World Mental Health Day on October the 10th, we asked our software engineers on how they take care of their mental wellbeing.

Here’s what they said

1. Fresh air

Our interim VP of Engineering swears by fresh air and regular doses of sunshine; living in Hamburg means that biking to work or going for a lunchtime stroll around the Alster is easy to integrate into your daily routine.

2. Meditation

Some of us have trouble not thinking about things, so meditation can help you put things into perspective and arrive in the current moment rather than being swept away by thought.

3. The Pomodoro Technique

This technique focuses on working in 25 minute sessions throughout the day.
This can be helpful for focussing efficiently and it encourages you to take regular breaks so that you avoid sitting for hours and hours without moving, drinking water or looking away from your computer screen.

4. Connect with colleagues

At Taxdoo, we encourage everyone to set up or attend ‘Donuts’ (informal online meetings), regardless of department.
Catching up with a colleague over a random Donut or setting up a Developer Donut is a great way to get to know your colleagues and helps you to feel connected and part of a team.

5. Take a break

Coding is awesome and the feeling of completing a task is great, however rest is also important. Why not use your lunch break to talk a walk around the block? You might be surprised at how refreshed you feel.

6. Find a job you love

We all spend a lot of time at work, so it is important to feel comfortable and motivated by your tasks, colleagues and environment.

How do you look after your mental health as a developer?

If you’re wondering what it’s like to work at Taxdoo, we can confirm that we’re hiring! For more details, check our jobs page or follow us on Twitter for more updates.

Today is World Youth Skills Day so we want to celebrate young people’s access to training and career opportunities by providing some inspiration for anyone interested in working in tech.

We asked our developers, product managers and even our CEO about the resources that sparked their interest in tech and helped to hone their skills; here are their recommendations.


Practise this

If you have 30 minutes a day, try completing a few exercises on FreeCodeCamp. This is a great way to learn to code in bitesize amounts of time.


Try this

A good starting point are the Hello World courses on Codecademy, which helps with learning tooling set-up and understanding how things work in that language. You can also test your knowledge by modifying the code and adding other statements.


Get stuck into this

A slightly more advanced course for learning frontend development is ToDoMVC, which also has a lot of other examples for each tech stack.


Build this

Learning by doing is the best method which this C# Unity Game 3D course from gamedev.tv definitely will help you to do so.


Read this

Clean Code is the classic book written by Uncle Bob, otherwise known as Robert Cecil Martin, who epitomizes the concept of clean code. From a product perspective, check out Lean Product Playbook.


Watch this

Silicon Valley (HBO)
our co-founder, Matthias Allmendinger, recommends this series as it sparked the entrepreneurial spirit that brought Taxdoo to life.


Eat this

Sushi. Ok, just kidding. Although our developers do love a good Bento box, feel free to pick your favourite brain food for learning to code. Happy hacking doesn’t happen on an empty stomach!

We hope this list was helpful! It is, of course, by no means complete- what else would you recommend for budding developers

We’re growing at Taxdoo! Follow us on Twitter or LinkedIn for the latest news and job posts from us.