Search code examples
pythonruby-on-railsdjangorubyintegration

Two backends, one DB - what's the best way to integrate data with Django?


Currently I'm working in a project that will add a functionality to a legacy application.

For a number of reasons that are completely out of my control, this functionality will resides in a different backend.

Not only that, each backend is actually in a different programming language (legacy is ruby, using Rails, new is Python, using Django), and different architectures (legacy is a monolithic app, django will provide a Rest API for a React frontend).

So far, so good. I've already solved how to integrate user's sessions, and mapped each table as a django model using the lifesaver command python manage.py inspectdb > legacy_models.py.

Now, I've got a bigger issue in my hands. Some of the old tables are not normalized, or there're missing fields which I'll need for the new application. I can't really mess with the old tables and create new fields, alter relationships or anything like that, since this legacy app is maintained by a separate corporation and we can't really depends on changes by their side, nor can we guarantee that changing the old tables won't mess with anything in this application.

Therefore, I've took another route, and decided to create tables on Django that'll extend the old tables with the info I'll need.

To make things easier to visualize, just imagine a table like Employee, but this table do not have a field productivity (that will be calculated in the new application). So I'd create a new table such as EmployeeDetails that will contain a OneToOne relationship with employee + any new field I need.

The issue here is:

I'd need to guarantee that any new Employee instance (that are being created in the legacy app) have a EmployeeDetails.

I've thought of two ways to do that:

  • Using a Celery Beat Task and a separate DB table to keep track of the last Employee synced, in order to verify if new employees were created.

  • Create a Postgres Trigger.

I'm tempted to use the first solution, since it's not possible for it to break anything in the legacy application (I mean, who knows what might happen in the legacy application during this employee trigger...)

I'm seriously not confident about this approach though. This is not how I like to design my backends, but the more I look in the legacy app the more my brain screams DITCH THE LEGACY, REDO THIS IN DJANGO THE CORRECT WAY... but well, I'm not allowed to do that...

Any thoughts about this?


Solution

  • Unfortunately in the situation described you don't have very many good options, but a celery beat task to ensure the existence of the extra OneToOne will either fire way too often using a ton of resources for nothing, or not fire often enough and there are time delay gaps you have to code for. I'm sure you've also evaluated that modifying the database and adding triggers could have a number of unexpected and undesired effects in the legacy code.

    My instinct in the situation is to modify the constructor, but Django really discourages changing Model constructors for various reasons. However, we can still achieve this functionality by connecting to formal signals Django fires off. You'd be looking at a pre-init signal or possibly post-init you'll have to test.

    https://docs.djangoproject.com/en/4.2/ref/signals/#pre-init

    In theory if you connect code to the pre-init signal of the original model it could call out to extended model table and ensure the relationships necessary exist before any code fires to work with the model. I think this approach would much more closely achieve what you're attempting to enforce without a lot of extra hassle and dead network calls.

    Also feel free to tell your boss I personally +1 nuking that legacy app in favor of a properly written Django app. The situation described is one where I'd happily declare technical bankruptcy.