Contents

Writeup of Technical Challenge - Integration

Recently I applied for a software engineering position in a team that does integration between third-party software and a proprietary system. One of the stages of the selection process was a take-home challenge that, as I undertand, mimics a lot of what is done on a day to day basis on the job.

We have two systems: a customer system (let us call it CS) that reads and writes JSON and the proprietary system (PS) represented by a MongoDB instance. The missing link is the software I had to write, the middle layer: a program whose responsability was to translate and sync messages from CS to PS and vice-versa. The challenge was to write such program in a fashion that would make it simple to add other third-party systems in the future. Extra points for error handling, clear logging, testing.

The language used in implementation was Python. I already has some experience with it, but man… did I learn a lot with this challenge. This post is to talk about the main learnings I had.

1. The architecture

First of all, it is import to say that I still do not have professional experience with software development. This means that most of my projects did not have to have a very complex architecture. Modular code: OK. But not architecture. This was the first time that I had to stop and think about it. Also, I read a lot about architecture, but it never really “clicked”. Until this challenge. The image below synthetizes what I implemented:

Architecture diagram

The directory structure of the project was as follows:

Project directory structure image

src/adapters/customer_system_adapter.py defines a class called CostumerSystemAdapter whose only jobs is to read json and write json. Similarly, src/adapters/proprietary_system_adapter.py defines class ProprietaryaSystemAdapter whose only jobs is to connect to the MongoDB instance and interact with it retrieving and sending data when necessary.

We used Pydantic library to instantiate in-memory objects from the JSON read from CS and BSON read from PS. Pydantic makes it very easy to guarantee that an instantiated object will follow strict rules (like having datetime attributes that are UTC-aware).

The translation between system occured in src/services/translator.py.

So, in case a new third-party system was to be add, we needed:

  1. An additional adapter
  2. An additional pydantic model
  3. An additional translantion from and to the new pydantic model
  4. Adjust the src/main.py for the newly added classes and functions

We would not have to touch the previously defined adapters and pydantic models.

In big projects, we could even define an Abstract Base Class (Python’s attempt at interfaces) in order to formalize what methods should be implemented in order to have a valid adapter for new systems.

2. Pytest fixtures

One of the deliverables was an end to end test using Pytest. I had already managed to implement an end to end test using a python script to generate sample data, a docker-compose.yml to instantiate a MongoDB database (and collection, aka table) and a Makefile to “orchestrate” everything. However, I did not know how to do that using Pytest.

Then, interacting with Claude Sonnet 4.5 I began to understand a little bit of Pytest Fixtures. They are user-defined contexts that can be used in tests (they are passed to test as parameters). We can use fixtures to define custom values for environment variables, as well as set connections to mocked databases.

So, to perform and end to end test using Pytest, all I had to do was to write a fixture that generated sample data and connected to a mock database, pass this fixture to the test and then call the program entrypoint from the test. Then pytest did its magic to organize output and report it.

3. Decorators

Just like architecture, I had already read about decorators and their usefullness. However, I had never encountered the need to use them in any of my projects. Until now.

As said above class ProprietarySystemAdapter contained methods to connect and interact with a MongoDB instance. One of the requisites of the technical challenge solution was to have some resilience policy with regards to the database. So, if the connection between the program and the database was lost for a brief moment, the program should not crash - it should try to reconnect a number of times before declaring a critical failure.

To handle connection with MongoDB, I use Motor, an async Python driver.

So, at every method that interacted with the database, a potential connection failure could occur. Naively, I had to write try/except blocks in every method to handle this potential problems. This is where decorators enter the scene.

A decorator is a function that takes a function as a parameter and returns a new function. This means that it is a way to add behavior to a previously defined function without having to rewrite said function.

In our case, I wrote a decorator to handle with connection issues and then I “wrapped” all methods into this decorator. This saved a ton of lines of code and made it much more elegant. Decorator definiton is below:

def retry_on_mongodb_error(func):
    """Decorator for MongoDB error handling with one extra retry"""

    @wraps(func)
    async def wrapper(*args, **kwargs):
        try:
            return await func(*args, **kwargs)
        except PyMongoError as e:
            logger.error(f"MongoDB error in {func.__name__}: {e}")
            logger.info(f"Retrying {func.__name__}...")
            try:
                return await func(*args, **kwargs)
            except PyMongoError as retry_error:
                logger.error(f"Retry failed: {retry_error}")
                logger.critical("Shutting down due to MongoDB errors")
                sys.exit(1)

    return wrapper

And then, we used it to wrap functions:


    @retry_on_mongodb_error
    async def check_connection(self):
        await self.collection.find_one({}, projection={"_id": 1})
        logger.info(
            "MongoDB instance, database and collection are recheable. Proceeding..."
        )

Conclusion

I learned a lot and am happy that I took the time to write this post. Here’s to hoping that I get this job. 🤞