[GO] J'ai essayé d'étendre le framework Active Record de Python

Quand j'ai rejoint l'entreprise, c'était Active Record

À l'été 2015, j'ai rejoint une startup d'IA. C'était un développement décent pour la première fois en 5 ans.

J'utilise Python Django (plus tard Google App Engine de Python). C'est un framework très similaire à Rails. J'ai touché Rails il y a environ 7 ans. J'ai donc pu suivre l'idée du cadre.

Outburst incompetence, outburst anti-pattern

Cependant, le code réellement en fonctionnement était terrible. Et le code de l'ingénieur serveur qui a rejoint l'entreprise il y a environ un mois contenait le charme de Medapani.

Vous trouverez ci-dessous une liste de symptômes visibles.

J'étais confus.

Pourquoi est-ce arrivé

J'ai pensé à la cause.

Cause 1

Je ne peux pas concevoir et analyser en premier lieu

――Il n'y a pas du tout de document disant "car cela changera plus tard"

Cause 2

Je ne connais pas les modèles d'architecture d'application d'entreprise

--Il y avait un putain d'ingénieur qui voulait connaître l'architecture uniquement avec Rails «Non, je sais que Rails a un bon ingénieur. .. ..

Cause 3

Le directeur est un vendeur et est trop ignorant de la fabrication et de la fabrication de produits informatiques. L'un des anti-modèles de démarrage est que les directeurs des ventes commencent sans une bonne compréhension du secteur technologique.

Contre-mesures

La cause 1 a tenté une session d'étude du processus ICONIX. Cliquez ici pour le livre de référence.

Parlons de la solution pour la cause 2.

Informations supplémentaires sur le modèle d'enregistrement actif

C'est trompeur, mais le modèle Active Record n'a pas commencé avec Rails. Il est décrit dans [Enterprise Application Architecture Pattern] de Fowler (https://www.amazon.co.jp/dp/B01B5MX2O2).

Je comprends que les tables View et DB sont 1: 1 et que View: Model: Table est 1: 1: 1 si vous incluez le modèle entre les deux. Il est adopté lorsque la configuration simple, le service simple et les fonctions compliquées ne sont pas ajoutés.

Cependant, au fur et à mesure que le système devient plus complexe et que les tables et les modèles se développent, cela devient difficile. Il est facile de se tromper.

Bien qu'il chevauche les symptômes ci-dessus, je pense que le problème peut également être classé en plusieurs modèles. Le contenu correspondant au modèle de problème est décrit ci-dessous.

Modèle de problème 1: la logique est intégrée dans la couche de présentation

J'ai limité la vue et l'interface par lots à seulement trois choses.

--Validation des paramètres de requête et d'argument

Modèle de problème 2: il existe un processus à combiner avec d'autres modèles

Introduction de la couche de service

Le terme architecture orientée services est utilisé ici. Un type si ordinaire.

ServiceLayerSketch.gif

Modifié pour exécuter les méthodes de couche de service à partir des vues et des interfaces de lot

Arrangement des termes

Le contrôleur d'application est applicable dans la conception pilotée par domaine. Notez qu'il est différent de ce que l'on appelle un service dans une conception pilotée par domaine (voir Classification Evans).

Il existe également un modèle de présentation Web dans le modèle d'architecture d'application d'entreprise, qui possède également un contrôleur d'application.

Oh, c'est déroutant.

Règles de dénomination

Fournit une API grossière pour les vues. Les règles de dénomination sont divisées selon les deux modèles suivants.

--CRUD: XxxService avec un seul modèle (tableau) --Combinez plusieurs modèles (tableaux) CRUD: YyyEngine

Singleton

Il est également inutile de créer une instance à chaque fois que vous l'appelez depuis une vue. Recette pour la singletonisation avec le décorateur de classe a été adoptée.

QuerySet personnalisé

Si JOIN est plus rapide dans le traitement par lots, il peut être librement utilisé dans un modèle étendu avec un QuerySet combiné. Décrivez le cas où la clé étrangère est apposée.

Diagramme ER supposé

Parfois, j'ai créé un modèle avec des informations supplémentaires sur l'élément plus tard.

Untitled Diagram.png

Traitez ces données JOIN comme s'il s'agissait d'un modèle.

Modèle de base

Ça ressemble à ça.

models/item.py


class Item(models.Model):
    name = models.CharField(max_length=128, blank=True)
    description = models.CharField(max_length=1024, blank=True)
    image = mdoels.ImageField(upload_to='hoge', blank=True, null=True)

models/item_feature_1.py


class ItemFeature1(models.Model):
    item = models.ForeignKey(Item)
    feature_1 = models.TextField()

models/item_extra_info.py


class ItemExtraInfo(models.Model):
    item = models.ForeignKey(Item)
    info = models.TextField()

QuerySet, Manager, Model for join

QuerySet pour rejoindre ressemble à ceci. En supposant que vous rejoignez select, rien d'autre n'est pris en compte.

joined_query_set.py


class JoinedItemFeatureQuerySet(models.QuerySet):
    def __iter__(self):
        queryset = self.values(
            'id', 'name', 'description', 'image',
            'itemfeature1__feature_1',
            'itemextrainfo__info')

        results = list(queryset.iterator())

        instances = []
        for result in results:
            item_feature = self.model(
                result['id'],
                result['name'],
                result['description'],
                result['image']
            )
            item_feature.feature_1 = result['itemfeature1__feature_1']  #Ajouter / emballer les propriétés du modèle
            item_feature.info = result['itemextrainfo__info']

            instances.append(item_feature)

        return iter(instances)  #Reconditionner au protocole de l'itérateur

custom_manager.py


class JoinedItemFeatureManager(models.Manager):
    def get_queryset(self):
        queryset = JoinedItemFeatureQuerySet(self.model, using=self._db)
        queryset = queryset.filter(del_flg=False, itemfeature1__isnull=False, itemextrainfo__isnull=False)  #Ne soyez pas nul
        return queryset

joined_item_domain.py


class JoinedItemFeatureDomain(Item):
    objects = JoinedItemFeatureManager()

    class Meta:
        proxy = True  #Ne créez pas de tableau.

Vous pouvez utiliser les données librement avec join_item_features = JoinedItemFeatureDomain.objects.filter (...). .. .. Haz.

Modèle de problème 3: le traitement du modèle est trop / complexe

Les méthodes réutilisables sont modérément abstraites (système d'apprentissage automatique, etc.)

Il peut être découpé en tant que stratégie ou réutilisé en tant que classe de calcul uniquement.

――Pour les choses qui peuvent être réutilisées plus tard ou qui peuvent être transformées en bibliothèque unique d'une entreprise

Le modèle n'est associé qu'à une table et le traitement est implémenté dans une sous-classe

Dans Django, il existe une méthode appelée modèle proxy, et je la place dans une sous-classe.

Ci-dessous, un extrait du code de mon projet personnel.

intro/models/abstract_model.py


from django.contrib.auth.models import User
from django.db import models


class AbstractModel(models.Model):
    registered_at = models.DateTimeField(auto_created=True)
    registered_by = models.ForeignKey(User, related_name='%(class)s_registered_by')

    updated_at = models.DateTimeField(auto_now_add=True)
    updated_by = models.ForeignKey(User, related_name='%(class)s_updated_by')

    class Meta:
        abstract = True

intro/models/article.py



from django.contrib.auth.models import User
from intro.models.abstract_model import AbstractModel

class Article(AbstractModel):
    title = models.CharField(max_length=128)
    description = models.CharField(max_length=2048)
    author = models.ForeignKey(Author)  #La description de la classe Author est omise
    categories = models.ForeignKey(Category, null=True, blank=True)  #Catégorie Description de la classe omise
    url = models.URLField()

intro/models/article_domain.py


from intro.models.article import Article
from intro.consts import DEFAULT_MECAB_PATH

class ArticleDomain(Article):
    class Meta:
        proxy = True

    def __init__(self, *args, **kwargs):
        # Do initialize...

    def __call__(self, mecab_path=None):
        if not mecab_path:
            self.mecab_path = DEFAULT_MECAB_PATH

    def parse_morpheme(self):
        # Do morpheme analysis

    @classmethod
    def train(cls, filter_params)
        authors = filter_params.get('author')
        articles = None
        if authors and articles:
            articles = Articles.objects.filter(authors__in=authors)
        # Do some extraction

        # Do training


    def predict(self, texts)
        # Do prediction

Présentation du code de test

Créez un test unitaire avec from django.test import TestCase lors de la refactorisation De toute évidence, les perspectives pour le code ont été faussées

Implémentation de sous-classes avec différentes conditions d'extraction de données (avec QuerySet personnalisé)

Modification de la condition d'extraction de données dans get_queryset dans la classe CustomManager afin que le CustomManager soit conservé dans la classe proxy.

Cela fonctionnait bien avec un modèle de domaine complexe pour les lots.

L'architecture hexagonale est adoptée pour la transmission de courrier électronique / la coopération de services externes

Ecrire une implémentation d'envoi de courrier électronique dans le modèle ou dessiner solidement urllib2 pour la liaison vers des services externes est gênant.

2304.gif

Création d'une classe Gateway pour la coopération externe (écrite comme adaptateur dans la figure ci-dessus), et hérité et utilisation de la classe Gateway dans la classe de modèle de domaine ou la classe de modèle (écrite comme Application dans la figure ci-dessus). ..

Les classes de modèle utilisent généralement des méthodes de commodité héritées des superclasses. Ensuite, nous avons hérité de la classe Gateway et utilisé des méthodes pratiques.

finalement

En ce qui concerne la cause 3, j'ai décidé de renoncer à la gestion et de changer de poste (même si seul le CTO est décent).

Il aurait peut-être été possible d'améliorer l'entreprise si nous avions pris le temps. Cependant, compte tenu du temps qu'il faut aux gens pour changer, de la vitesse à laquelle la technologie progresse et des conditions d'autres entreprises, j'ai pensé que si je restais, je perdrais mon temps et ma vie. Je ne suis pas assez jeune pour tolérer un tel gaspillage. Je ne suis pas assez jeune pour gérer les souvenirs des autres qui sont seuls.

Non seulement pour les startups, mais la direction l'a reconnu comme très important.

Recommended Posts

J'ai essayé d'étendre le framework Active Record de Python
J'ai essayé de toucher la bibliothèque GUI de Python "PySimple GUI"
J'ai essayé de déboguer.
J'ai essayé d'apprendre PredNet
J'ai essayé d'organiser SVM.
J'ai essayé de réintroduire Linux
J'ai essayé de présenter Pylint
J'ai essayé de résumer SparseMatrix
jupyter je l'ai touché
J'ai essayé d'implémenter StarGAN (1)
J'ai essayé d'implémenter Deep VQE
J'ai essayé de toucher Python (installation)
J'ai essayé de mettre en place une validation contradictoire
J'ai essayé d'expliquer l'ensemble de données de Pytorch
J'ai essayé l'authentification vocale Watson (Speech to Text)
J'ai touché l'API de Tesla
J'ai essayé de m'organiser à propos de MCMC.
J'ai essayé d'implémenter Realness GAN
J'ai essayé de déplacer le ballon
J'ai essayé d'estimer la section.
J'ai essayé de créer un linebot (implémentation)
J'ai essayé de résumer la gestion des exceptions Python
J'ai essayé d'implémenter PLSA en Python
J'ai essayé d'utiliser Azure Speech to Text.
J'ai essayé d'implémenter Autoencoder avec TensorFlow
J'ai essayé de résumer la commande umask
J'ai essayé d'implémenter la permutation en Python
J'ai essayé de créer un linebot (préparation)
J'ai essayé de visualiser AutoEncoder avec TensorFlow
J'ai essayé de reconnaître le mot de réveil
J'ai essayé de commencer avec Hy
J'ai essayé d'implémenter PLSA dans Python 2
Entrée standard Python3 que j'ai essayé de résumer
J'ai essayé de classer le texte en utilisant TensorFlow
J'ai essayé de résumer la modélisation graphique.
J'ai essayé d'ajouter un post-incrément à l'implémentation CPython
J'ai essayé d'implémenter ADALINE en Python
J'ai essayé de laisser optuna résoudre le nombre
J'ai essayé d'estimer le rapport de circonférence π de manière probabiliste
J'ai essayé de toucher l'API COTOHA
J'ai essayé d'implémenter PPO en Python
J'ai essayé d'implémenter CVAE avec PyTorch
J'ai créé une API Web
J'ai essayé de résoudre TSP avec QAOA
[Python] J'ai essayé de calculer TF-IDF régulièrement
J'ai essayé de toucher Python (syntaxe de base)
J'ai essayé de raccourcir petit à petit le FizzBuzz de Python
J'ai essayé de comparer le cadre d'application Web
J'ai fait de mon mieux pour retourner au Lasso
J'ai essayé de résumer les modules d'Ansible - l'édition Linux
J'ai essayé le framework de test Python Tornado
J'ai fait un script pour enregistrer la fenêtre active en utilisant win32gui de Python
J'ai essayé de prédire l'année prochaine avec l'IA
J'ai essayé de programmer la bulle de tri par langue
J'ai essayé Web Scraping pour analyser les paroles.
J'ai essayé d'implémenter la lecture de Dataset avec PyTorch
J'ai essayé d'utiliser lightGBM, xg boost avec Boruta