Máquinas de estado e django

Ao trabalhar em um projeto de django, há uma série de bibliotecas de terceiros obrigatórias se você não quiser reinventar a roda indefinidamente. Uma ferramenta para depurar consultas sql (debug-toolbar, silk, --print-sql de django-extensions), algo para armazenar estruturas de árvore, tarefas periódicas / adiadas (a propósito, uswgi tem uma interface parecida com o cron . EAV ainda é necessário, embora muitas vezes possa ser substituído por jsonfield. E uma dessas coisas extremamente úteis, mas por alguma razão menos discutida na rede, é FSM. Por alguma razão, eu não os encontro com frequência no código de outra pessoa.





Quase todos os registros do banco de dados têm algum estado. Por exemplo, para um comentário, ele pode ser - publicado / excluído / excluído por um moderador. Para fazer o pedido em uma loja - emitido / pago / entregue / devolvido, etc. Além disso, a transição de um estado para outro é frequentemente manchada pelo código e há lógica de negócios nela, que deve ser abundantemente coberta com testes (você ainda precisa, mas pode evitar testar coisas elementares, por exemplo, que um pedido pode ir para o estado "reembolso" somente depois de estar na condição "pago".





Seria bastante lógico descrever essas transições de forma mais declarativa e em um só lugar. Junto com a lógica necessária e verificação de acesso.





Aqui está um exemplo de código dos testes da biblioteca django-fsm





class BlogPost(models.Model):
    """
    Test workflow
    """
    state = FSMField(default='new', protected=True)

    def can_restore(self, user):
        return user.is_superuser or user.is_staff

    @transition(field=state, source='new', target='published',
                on_error='failed', permission='testapp.can_publish_post')
    def publish(self):
        pass

    @transition(field=state, source='published')
    def notify_all(self):
        pass

    @transition(field=state, source='published', target='hidden', on_error='failed',)
    def hide(self):
        pass

    @transition(
        field=state,
        source='new',
        target='removed',
        on_error='failed',
        permission=lambda self, u: u.has_perm('testapp.can_remove_post'))
    def remove(self):
        raise Exception('No rights to delete %s' % self)

    @transition(field=state, source='new', target='restored',
                on_error='failed', permission=can_restore)
    def restore(self):
        pass

    @transition(field=state, source=['published', 'hidden'], target='stolen')
    def steal(self):
        pass

    @transition(field=state, source='*', target='moderated')
    def moderate(self):
        pass

    class Meta:
        permissions = [
            ('can_publish_post', 'Can publish post'),
            ('can_remove_post', 'Can remove post'),
        ]
      
      



Isso é ótimo para api de descanso, entre outras coisas. Podemos criar pontos de extremidade para transições entre estados automaticamente. Por exemplo, o pedido / pedidos / id / cancel parece uma ação perfeitamente lógica para um conjunto de visualizações. E já temos as informações necessárias para verificar o acesso! E também para botões no painel de administração e a capacidade de desenhar belos gráficos com fluxo de trabalho :) Existem até editores de fluxo de trabalho visuais, ou seja, não programadores podem descrever processos de negócios





Quanto mais código declarativo e genérico escrevermos, mais confiável ele será. Menos código, menos duplicação, menos bugs. O teste é parcialmente transferido para o autor da biblioteca e você pode se concentrar na lógica de negócios exclusiva do projeto.








All Articles