Executando verificação de segurança...
8

É má prática criar uma função que retorna uma classe?

Contexto completo Essa é a minha primeira vez usando Flask para criar uma RESTful API. Estou usando Flask, Flask-RESTful, Marshmallow e SQLAlchemy. Decidi usar orientação a objetos no projeto por me sentir mais confortável com essa forma de organização em projetos grandes.

No banco de dados, tenho 7 tabelas diferentes. Como o MVC é o único padrão arquitetural com o qual tenho familiaridade, tentei seguir algo que mais ou menos se encaixe nisso (percebi ao longo do desenvolvimento que não seria possível fazer tudo em MVC, então foram feitas algumas adaptações). Sendo assim:

  • Tenho 7 classes Model para representar os dados do banco no código;
  • Para cada classe Model, há uma classe Schema, que serve para serializar/desserializar os dados que a API recebe/envia. Essa classe é um padrão do Marshmallow pelo que entendi;
  • Para cada Model, há também uma classe Service, que, no momento, interage com o SQLAlchemy e processa regras de negócio;
  • Para cada Service, há um Resource, que lida com as requisições HTTP e chama os métodos da Service.

Isso significa que, para cada tabela, tenho 4 classes. O problema é que, estruturalmente, as classes Schema e Resource são sempre idênticas, apenas adaptando qual outra classe elas vão chamar. Exemplo: todas as classes Resource possuem o método get. Porém, a UserResource chama a UserService nesse método, enquanto a TeamResource chama a TeamService, e por aí vai.

As classes Service possuem regras distintas, e as Model têm campos diferentes, então não tem pra onde correr. Mas nos outros casos, me parece repetição de código desnecessária, o que traz todas aquelas questões de manutenibilidade, redundância e por aí vai.

TL;DR: Em uma API, tenho várias classes idênticas para cada tabela que ela pretende acessar. Os grupos mais redundantes de classes são Resource e Schema.

No caso da Resource, usei herança e a situação ficou menos pior. Por outro lado, no caso da Schema, estou pensando em fazer uma função que retorne uma classe Schema de acordo com o Model passado como parâmetro. Seria má prática, considerando que elas possuem estrutura idêntica, com exceção do model?

Segue a estrutura das Schema:

class UserSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = UserModel
        
    @post_load
    def to_model(self, data: dict, **kwargs) -> UserModel:
        return UserModel(**data)

O que pretendo fazer é:

def create_schema(model) -> type:
    class ModelSchema(SQLAlchemyAutoSchema):
        class Meta:
            model = model
        
        @post_load
        def to_model(self, data: dict, **kwargs) -> model:
            return model(**data)
    
    return ModelSchema

Obs.: quanto ao padrão arquitetural, estou ciente que por enquanto parece uma grande mistura. Peço que foquem apenas na pergunta do título.

Carregando publicação patrocinada...
10

Olha, criar High Order Classes é uma forma de se atingir herança horizontal em linguagens que suportam isso, inclusive acredito que Python suporte isso nativamente o que torna um pouco sem sentido criar uma High Order Class, e se herança vertical já não é bem vista, a horizontal tem uma reputação ainda pior, inclusive um bom motivo para herança num geral não ter uma boa reputação é quando você consegue utilizar as duas ao mesmo tempo, pois isso pode acabar levando ao que as pessoas chamam de problema do diamante.

A princípio herança seja ela qual for é uma técnica válida de reúso de código, porém com certeza essa não seria a minha recomendação para resolver um problema.

Eu não sou da comunidade python, então não faço ideia de como seria algo idiomático para um programador python, entretanto dei uma olhada nessa lib Marshmellow, e me parece algo como o Zod do JS com umas baterias a mais.

Olhando o exemplo que tem na doc dele me parece que eles tem uma solução oficial para a criação dos Schemas:
https://marshmallow-sqlalchemy.readthedocs.io/en/latest/recipes.html#automatically-generating-schemas-for-sqlalchemy-models

Agora pensando em uma boa arquitetura, mesmo que os dados sejam os mesmos entre várias classes, você não deveria ficar tentado a compartilhar eles por qualquer meio que seja, ainda mais herança que vai gerar um acoplamento enorme. Isso só deve acontecer se ambas as classes mudarem pelos mesmos motivos, senão apesar de parecerem iguais no início, conforme o tempo for passando, elas vão terminar muito diferentes uma da outra, e esse acoplamento inicial feito para reaproveitar o código é o que vai estar segurando uma de evoluir independente da outra.

Quanto ao problema de sentir que muitas classes são iguais/repetidas, isso provavelmente se deve a criação de abstrações desnecessárias. Seu projeto provavelmente deve ser tão simples que acaba por ter um modelo anêmico, e portanto ter algumas abstrações extras (um dos 4 esteriotipos de classes) pode acabar por apenas servir para repetir o código enquanto causa alguma indireção, sem agregar algo de fato ao projeto, sendo um problema de overeengenering.

Claro que como arquitetos gostamos de deixar nossas opções abertas para o futuro, mas criar abstrações demais no início pode ser um problema tão grande quanto a falta delas no longo prazo.

1

Caraca, que resposta completa.

Realmente, passei batido por essa parte da documentação. Parece resolver meu problema, mas acho que vou dar um passo para trás e me atentar à questão do excesso de complexidade. O que você falou faz bastante sentido.

Obrigado pela ajuda! :)

2

Opa amigo, tudo bom? como é quase a mesma coisa, será que você não consegue adaptar ao padrão strategy? ai sim retornar a classe correspondente que você deseja?

1
1

Recomendo que p
utilize os padrões strategy e factory. procure no refactor.guru.
idealmente e visando a separação de código você faria uma fábrica pra cada classe e uma outra pra agregação. Porém precisa de verificar qual a melhor forma de faze-lo.
O padrão strategy pode te ajudar com isso.

Não conheço muito de python mas verifique a possibilidade de utilizar polimorfismo.

1