-1

I am recently having a discussion with colleagues about where should a piece of code reside within our django project.

For the sake of the question, let's say we are creating APIs for uber-like quotation feature

We have the following modules(an app in django's lingo), each concerns with persistence, business logic and API

  • location, for GPS storage and distance calculation
  • traffic, for travel time estimation
  • promotion, for discount using promo codes
  • quotation, for price calculation, currently depends on location only

Now we want to have an API for quotation to also take promotion into account.

It comes natural to me to put the API under quotation module, extending existing API behaviour, and adding promotion as a dependency.

However my colleague advocates otherwise - to create a new API under promotion and adding quotation as a dependency.

While the above example is for illustration only, I would like to hear what, in general, considerations should be made when placing cross cutting code?

Related:

Cross-cutting concerns in package-by-feature structure

Folder-by-type or Folder-by-feature


Edit: A comment I find useful

Two services can be pretty well designed at one point in time, with isolated use cases, and next year someone comes up with a new requirement which requires cross-domain access - that is how reality works. Throwing the old system away and build a new one with just one service instead of two is not even impractical, it seldom results in a better system.

  • 1
    Is a separate peer-level feature, some quoted-promotion, even a consideration? It could depend equally on both quotation and promotion. – 9000 Aug 23 '19 at 16:36
  • @9000 Definitely could! One concern for me is the extensibility the current API. e.g. later on we will want to have a quotation based on traffic, and also have quotation based on both traffic and promotion. And obviously any combination of traffic, location and promotion. Second concern is the integration of said API. i.e. how to decide which API to invoke based on different condition. Another one is maintainability of the codebase – user2829759 Aug 23 '19 at 16:42
  • It's almost as if you have a <thing> that can depend on one or several factors, like {traffic, location} or {quotation, promotion}, etc. Can you extract and name this thing? Maybe it already exists under some name, but is not generalized enough? – 9000 Aug 23 '19 at 17:00
2

I want to propose an entirely different approach, instead of creating cross-module dependencies.

As I understand, Quotations are calculated based on one or more factors such as location, traffic, and promotions. More elements may be added in the future, and a permutation/combination of these factors may be used in different situations.

The standard way (considering you have a monolithic Django project with multiple apps) would be to make calls from one app into another. Depending on who calls who, each app would understand the format and structure of the other app and know how to call a method with params and collect responses. The calls will also be serial, where the individual pieces are collected one by one and collapsed into one quotation.

Instead of doing this, you could use a reactive/message-driven architecture. You can bubble an event or send targeted messages to different applications. Each app will have subscribers waiting for these messages. They will pick up signals/events, process them, and put a response message back into the queue.

The quotation subscriber will wake up each time a response message comes through to see if it has the necessary info to collate; otherwise, its a no-op. On arrival of the last message, the Quotation application collates the response and responds to the callee.

Potential Architecture Advantages:

  • You keep each app isolated and will contain business logic only relevant to its domain
  • Each app can be potentially used in multiple parts of your system, without need to code individual calls
  • The system is parallelized, so you will construct a response much faster than if you do serial, synchronous calls.

Things you need to be aware of:

  • You are introducing a new message-driven architecture into the application
  • You need to choose a good reliable middleware, like RabbitMQ or Redis.
  • You need to build failover mechanisms to handle cases where a message to a particular subsystem does not come back in a meaningful amount of time. Most Uber-like systems tend to wait for some time, and then force a response: either fail or send a partially collated reply out.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.