0
\$\begingroup\$

I'd like to build an app in Flask that can switch between talking to a PostGresql and a Mongo DB. As I understand it, the Strategy Pattern is about being able to switch algorithms that are referred to by some method call on the fly. So, I think this pattern would be relevant because I want to switch out what abstract functions like create, read, update, delete do. It sounds to me like strategy would be relevant here, but I'm not quite sure how to implement it. Here's what I'm thinking:

I have this file to handle my routes:

from flask import Blueprint
from .models import Entity
api = Blueprint('api', __name__, url_prefix='/api')
from .models.EntityWrapper import Entity

@api.route('/entities', methods=['POST'])
def create_entity():
    entity = EntityWrapper(label="cheese")
    entity.save()
    return jsonify(entity)

@api.route('/entities')
def get_entities():
    entities = Entity.read()
    return jsonify(entities)

The save and read methods should do different things, depending on which database is being used. The class they belong to, EntityWrapper, reads the environment variables and selects which kind of Entity class should be exported:

import os
from .Entity_mongo import Entity as MongoEntity
from .Entity_postgres import Entity as PostgresEntity
from dotenv import load_dotenv, find_dotenv

load_dotenv(find_dotenv())
DB_TYPE = os.environ.get("DB_TYPE")

Entity = None

if DB_TYPE == 'mongo':
    Entity = MongoEntity
elif DB_TYPE == 'postgres':
    Entity = PostgresEntity
else:
    raise Exception("'DB_TYPE' improperly defined in .env")

Here's Entity_posgres.py

from .Model_postgres import PostgresModel

class Entity(db.Model):
    __tablename__ = 'entities'
    id = db.Column(db.Integer, primary_key=True)
    label = db.Column(db.String(), nullable=False)

Since there are a number of methods that should all be handled "the postgres way" for any object, I'm bundling these into a class called PostgresModel. (A mimilar class would be defined for Mongo, doing this "the mongo way", but for sake of brevity I'll only show postgres).

# from .ModelABC import ModelABC
from ..extensions import postgres_db as db

# class PostgresModel(ModelABC, db.Model):    
class PostgresModel(db.Model):
    def create(self, instance):
        # do some postgres create stuff
        db.session.add(instance)
        db.session.save()
    def read(self, cls):
        # do some postgres read stuff
        cls.query.all()
    def update():
        # do some postgres update stuff
        pass
    def delete():
        # do some postgres delete stuff
        pass

I can think of one way the stategy pattern might fit in here: PostgresModel could inherit from some ModelABC class, which would keep track of which strategy is currently being used to create, read, update, or delete. It seems to be this class would look something like the following. To tell the truth, I'm not even sure what advantages it could bring, but here's what it might look like:

from abc import ABC, abstractmethod

class ModelABC(ABC):
 
    def assign_strategy(self, strategy_key):
        pass

    def use_strategy(self, strategy, *args, **kwargs):
        if strategy == 'create':
            self.create_strategy(*args, **kwargs)
        elif strategy == 'read':
            self.read_strategy(*args, **kwargs)
        elif strategy == 'update':
            self.update_strategy(*args, **kwargs)
        elif strategy == 'delete':
            self.delete_strategy(*args, **kwargs)
        else:
            raise Exception('strategy name invalid')

    @abstractmethod
    def create_strategy(self):
        pass

    @abstractmethod
    def read_strategy(self):
        pass

    @abstractmethod
    def update_strategy(self):
        pass

    @abstractmethod
    def delete_strategy(self):
        pass

Am I on the right track with this strategy pattern, or is this not the right use case?

\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

The short answer is: You don't need the strategy pattern here. just do as nullTermiator said in his answer.

To understand why strategy does not fit here, first we need to understand the problem strategy supposed to solve, or formally, the intention of the strategy pattern.

In statically typed languages such as Java, C#, Kotlin, etc. you can't simply use two different classes interchangeably unless they share a common parent (Polymorphism)

In your case you would have a MongoEntity and a PostgresEntity, and you want your code to consume one of them without hard coding it (a.k. your code should not know which one it is consuming). To achieve this, two restrictions should be applied to those classes

  1. All classes must have the same methods' signatures.
  2. All classes must share a common parent (commonly an interface) which works as an abstraction for the intended operation. and your code should depend on this abstraction not a specific implementation of it.

In strategy you define an instance of this abstraction in your code, initialize it with the default implementation, and define a setter method to change the default implementation with the desired one.

That is simply the strategy pattern as defined in GoF book.

An enhancement usually made to this pattern utilizing what is called Constructor Dependency Injection. Instead of defining the abstraction in your code and use a setter method to change it, you pass it as an argument to the constructor of the consuming class. which you intuitively tried to do in your code, the pythonic way :)

Now back to your code.

  • First: Python is not statically typed. so the second restriction does not apply. it may be recommended as a conventional way to increase the code readability and organisation, but not mandatory.
  • Second: Your code consumes different classes, each of them is not just an algorithm, they have different states and multiple functionalities. Which is the intention of another family of design patterns (Creational) you can read about Factory, Factory Method and builder patterns.
  • Third: you need to inject the right Model Class based on configuration. This is the definition of DI (Dependency Injection) and that is all you need. Moreover, in python you can just define a global variable and initialize it with the right instance based on the configuration as you've already done in the Entity variable.

Conclusion:

Your code only needs the following:

  • Define a class for each DB Provider
  • Classes should have identical methods' signatures (You may use inheritance as a conventional way to say this is the contract of all those classes)
  • Use environment to initialize Entity with the right instance and import and use it in your code.
  • Finally the repeated but valuable advice. Don't use design patterns for the sake of using them. We all fall in this many times. so try your best :)
\$\endgroup\$
3
\$\begingroup\$

Typically you'd have two classes that implement the four methods create, read, update and delete, one for Mongo and one for PostGres. Create a factory method that determines what kind of DB you are talking to, and returns an instance of the appropriate class.

The rest of your code can interact with the class using the methods. It doesn't need to know what kind of DB you are using.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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