Logica aziendale separata dai modelli ORM in SQLAlchemy

2

Ho un'applicazione Flask con decine di modelli complessi, quasi tutti correlati tra loro.

Un semplice pseudo-schema di alcuni di essi:

+----------------+
|   FoodGroup    |
+-------+--------+
        ^
        |
+-------+--------+
|     Food       +<------+
+---+------------+       |                      +--------------+
    ^                    |                      | NutrientGroup|
    |                    |                      +-------^------+
    |                    |                              |
    |                    |                              |
+---+----------------+   +---------------+       +------+-----+
|  FoodMeasurements  |   |    FoodData   +------>+  Nutrient  |
+--------------------+   +---------------+       +------------+

Il mio obiettivo è disgiungere il più possibile la logica aziendale dai modelli ORM e incapsulare le operazioni dei dati in modo semantico e sicuro. Molte operazioni richiedono molta logica da molti modelli e voglio evitare il mixaggio diretto dei modelli ORM. Il primo pensiero è stato quello di creare un modulo di servizi e riempirlo con funzioni come altri suggerivano . Preferisco un approccio più OOP, quindi ho implementato un livello Manager che sarà responsabile delle operazioni sui dati.

ad es. non toccherò direttamente la FoodMeasurement. A FoodMeasurement appartiene al cibo. Allo stesso modo, FoodData appartiene anche al cibo e ha una relazione nutriente. Ho bisogno di un FoodManager per modificare i loro valori. (Nello schema db reale, Food è correlato a 9 tabelle.)

Al codice seguente proverò a spiegare il concetto. (Non è il codice originale, quindi scusami per errori e refusi.)

class BaseManager:
    def __init__(self, model):
        # The base class keeps a reference of the orm model
        self.model = model

    def save(self):
        # code for saving obj to db
        ...

    def create(self, **kwarg):
        raise NotImplementedError()
        # method which all children should implement

    def get_id(self):
        # similar methods will be on the children, like get_name etc
        return self.model.id

class FoodManager(BaseManager):
    def __init__(self, model: Food):
        # When the class is initiated it passes the orm model to the 
        # model property
        super().__init__(model)

    @classmethod
    def create(cls, name: str, food_id: int, food_group_id: int, 
        enabled: Optional[bool] = True):
        # When 'create' method is called it creates a FoodManager 
        # object with the ORM model assigned to the model property
        args = locals()
        args.pop("cls", None)
        food = Food(**args)
        return cls(food)

     @classmethod
     def get_food(cls, id):
         # Get a food by its id
         # Similar to 'create' method, but the ORM model comes from 
         # the db
         food = Food.query.filter_by(id=id).first()
         return cls(food)

     def get_measurements(self):
         return self.model.measurements.all()

     def get_nutrients(self):
         return self.model.nutrients.all()

     def set_measurements(self):
         # lots of code, logic, validation etc...
         self.model.measurements.append(data)
         # for chaining
         return self

     def set_nutrients(self):
         #similar to the above
         self.model.nutrients.append(data)
         return self

     def update_measurements(self):
         # ... checks for modified FoodMeasurement ORM models 
         # and acts accordingly ...
         return self


# Now I can create a food and save it to db
food = FoodManager.create(name="Banana", food_id=1, food_group_id=1)
food.set_nutrients(data)
food.set_measurements(data_2)
food.save()

# I can do oneliners too
FoodManager.create(**food_data)
\ .set_nutrients(**nutrients).set_measurements(**meas).save() 

# Furthermore, I can load a record from the db and do some operations
db_food = FoodManager.get_food(1)
db_food_meas = db_food.get_measurements()
# ... code for edit/remove/add measurements
db_food.update_measurements(db_food_meas).save()

# Finally, if I already have an orm model from another operation
orm_food = Food.query.filter_by(id=id).first()
etc_food = FoodManager(orm_food)
# ...

Raccomanderesti questo modello? Quali difetti incontrerò usando questo modello?

    
posta hambos22 26.09.2018 - 16:52
fonte

0 risposte

Leggi altre domande sui tag