Organizzazione della logica aziendale complessa (insieme di regole)

1

Ho 20% dientities e 50% diuser requests. E ho una logica aziendale molto complessa (insieme di regole). Come posso creare la mia architettura in modo da non essere confuso quando aggiungo nuove regole.

Ora se aggiungo la proprietà "InTheBattle" alla flotta, devo considerare questo in 5 requestDispatchers e 3 metodi in Verifications classes . Cosa succederà quando avrò il 100% dientities e il 500% diuser requests?

Forse devo usare qualche schema o strumento come Excel? Come posso contenere tutte le regole nella mia testa? :)

Entity = orm model class

Esempio di un dispatcher di richieste:

public class CreateShipRequestHandler : IRequestHandler
{
    private RepoContainer Repo;

    private int mFleetId;
    private int mShipDesignId;

    public void CreateShipHandler(RepoContainer repo, int fleetId, int shipDesignId)
    {
        this.Repo = repo;

        this.mFleetId = fleetId;
        this.mShipDesignId = shipDesignId;
    }

    public bool Dispatch(out string message)
    {
        //INTERESTING PLACE:

        message = string.Empty;

        var fleet = Repo.FleetRepo.GetById(mFleetId);
        var shipDesign = Repo.ShipDesignRepository.GetById(mShipDesignId);

        if(fleet == null || shipDesign == null)
        {
            message = "VerificationError";
            return false;
        }

        var userResearchedScience = Repo.UserResearchedScienceRepo.GetByUserAndScience(fleet.UserId, shipDesign.RequiredScienceId);

        //I am using ShipVerification on the client too (for activate buttons)
        if(!ShipVerification.IsCanCreateShip(fleet, userResearchedScience, out message))
        {
            return false;
        }

        var newShip = new Ship(mFleetId, mShipDesignId);
        Repo.ShipRepo.AddOrUpdate(newShip);

        return true;
    }
}
    
posta GLeBaTi 23.03.2017 - 19:30
fonte

3 risposte

2

Sei preoccupato per l'esplosione combinatoria, che potrebbe rendere difficile la manutenzione del tuo progetto.

Esplosione combinatoria

È difficile mantenere tutte le relazioni e le dipendenze tra tutte queste entità. Il modello di mediatore può aiutare a risolvere questo problema: Entities , Requests forse anche Rules sono colleghi e il mediatore racchiude le interazioni.

Motore regole

Hai molte regole aziendali e le regole si evolvono spesso. L'approccio migliore consiste nell'adottare un'architettura che implementa un motore di regole .

Ci sono diversi modi per implementarlo. Un modo potrebbe essere utilizzare una catena di responsabilità . Puoi trovare alcuni esempi di implementazione su wikipedia .

Un altro modo per implementare il motore di regole potrebbe essere quello di utilizzare un processore di eventi. Un evento (ad esempio, la nuova entità viene creata) viene prima inserito in una coda eventi. Il motore delle regole attiva tutte le regole rilevanti per questo evento e rimuove l'evento. Ogni regola può attivare altri eventi che vengono inseriti nel ciclo degli eventi. Una volta che la coda degli eventi è vuota, tutte le regole e le loro conseguenze vengono elaborate. Ovviamente, un motore di regole richiede un po 'più di intelligenza (ad esempio, identifica se le regole sono in sequenza, tigerring a vicenda all'infinito). Ma è già un buon inizio.

    
risposta data 23.03.2017 - 21:58
fonte
1

Se comprendo correttamente il tuo problema (devi gestire un gran numero di regole la cui logica dipende da un gran numero di proprietà dell'oggetto), potrebbe avere senso estrarre la logica in un formato alternativo (un "file delle regole") e utilizzare un generatore di codice per generare automaticamente il codice di invio e verifica.

Per quanto riguarda i formati alternativi, fai riferimento a Excel e è una possibilità. Personalmente, preferirei YAML, JSON o XML, penso. Fondamentalmente, devi solo scegliere un formato basato su testo che sia e leggibile da umani e facilmente analizzabile in uno script di generazione del codice.

Riguardo alla generazione del codice, la mia preferenza personale per gli ultimi anni è stata quella di utilizzare l'utilità Cog di Ned Batchelder. È uno script Python che elabora piccoli frammenti di codice Python incorporato inline all'interno delle tue fonti. Se suona bizzarro , credimi, è assolutamente in grado di cambiare la vita una volta che ti senti a tuo agio con l'approccio.

L'unico esempio che ho a portata di mano non corrisponde esattamente al tuo caso d'uso, ma qui c'è uno snippet di un'intestazione C ++ 'cogged' ( Datum.h ) che contiene bit di generazione di codice Python all'interno di un 'magic comment':

class Datum                                                                                                                                                                                                                                                                 
{
public:
    Datum(
        v1_0::acme::SocketCanStub& stub,
        std::string const& name,
        uint8_t offset,
        uint8_t length,
        bool bigEndian
    )
      : stub_(stub)
      , name_(name)
      , offset_(offset)
      , length_(length)
      , bigEndian_(bigEndian)
    { }

    virtual ~Datum() = default;

    void Process(struct can_frame const& msg);

    virtual void Update(uint64_t rawValue) = 0;
    boost::any const& Value() const { return value_; }

protected:
    v1_0::acme::SocketCanStub& stub_;
    std::string name_;
    uint8_t offset_;   // <-- in bits
    uint8_t length_;   // <-- in bits
    bool bigEndian_;
    boost::any value_;
};

/* [[[cog
  import socketcan.generator
  signals = socketcan.generator.parse_can_defs()
  for s in signals:
      cog.outl('class {}_Datum : public Datum'.format(s.name))
      cog.outl('{')
      cog.outl('  public:')
      cog.outl('    {}_Datum('.format(s.name))
      cog.outl('        v1_0::acme::SocketCanStub& stub,')
      cog.outl('        std::string const& name,')
      cog.outl('        uint8_t offset,')
      cog.outl('        uint8_t length,')
      cog.outl('        uint8_t bigEndian,')
      cog.outl('        uint64_t initval')
      cog.outl('    )')
      cog.outl('      : Datum(stub, name, offset, length, bigEndian)')
      cog.outl('    {')
      cog.outl('        this->Update(initval);')
      cog.outl('    }')
      cog.outl()
      cog.outl('    void Update(uint64_t rawValue) override;')
      cog.outl('};')
      cog.outl()
   ]]] */

Quando esegui cog.py su questo file usando:

$ cog.py -r Datum.h

viene eseguito il codice nel commento magico e il suo output viene inserito inline nel file, subito dopo il commento. L'output è simile a questo:

/* [[[cog
  import socketcan.generator
  signals = socketcan.generator.parse_can_defs()
  for s in signals:
      cog.outl('class {}_Datum : public Datum'.format(s.name))
      cog.outl('{')
      cog.outl('  public:')
      cog.outl('    {}_Datum('.format(s.name))
      cog.outl('        v1_0::acme::SocketCanStub& stub,')
      cog.outl('        std::string const& name,')
      cog.outl('        uint8_t offset,')
      cog.outl('        uint8_t length,')
      cog.outl('        uint8_t bigEndian,')
      cog.outl('        uint64_t initval')
      cog.outl('    )')
      cog.outl('      : Datum(stub, name, offset, length, bigEndian)')
      cog.outl('    {')
      cog.outl('        this->Update(initval);')
      cog.outl('    }')
      cog.outl()
      cog.outl('    void Update(uint64_t rawValue) override;')
      cog.outl('};')
      cog.outl()
   ]]] */
class VEH_SPEED_Datum : public Datum
{
  public:
    VEH_SPEED_Datum(
        v1_0::acme::SocketCanStub& stub,
        std::string const& name,
        uint8_t offset,
        uint8_t length,
        uint8_t bigEndian,
        uint64_t initval
    )
      : Datum(stub, name, offset, length, bigEndian)
    {
        this->Update(initval);
    }

    void Update(uint64_t rawValue) override;
};

class PRND_STAT_Datum : public Datum
{
  public:
    PRND_STAT_Datum(
        v1_0::acme::SocketCanStub& stub,
        std::string const& name,
        uint8_t offset,
        uint8_t length,
        uint8_t bigEndian,
        uint64_t initval
    )
      : Datum(stub, name, offset, length, bigEndian)
    {
        this->Update(initval);
    }

    void Update(uint64_t rawValue) override;
};

class TurnIndLvr_Stat_Datum : public Datum
{
  public:
    TurnIndLvr_Stat_Datum(
        v1_0::acme::SocketCanStub& stub,
        std::string const& name,
        uint8_t offset,
        uint8_t length,
        uint8_t bigEndian,
        uint64_t initval
    )
      : Datum(stub, name, offset, length, bigEndian)
    {
        this->Update(initval);
    }

    void Update(uint64_t rawValue) override;
};                                                                                                                                                    

// [[[end]]]

L'obiettivo è generare una piccola sottoclasse di Datum per ogni segnale CAN 'interessante' definito in un ish di grandi dimensioni (1,4 MB) kcd il file . È un po 'difficile da spiegare, ma ... ecco il file socketcan/generator.py completo a cui si fa riferimento nel commento Cog:

#!/usr/bin/env python

import bz2
from collections import namedtuple

SIGNALS = {

    'TurnIndLvr_Stat': (
        'v1_0::acme::SocketCan::TurnSignalState',    # <-- Value type
        'getTurnSignalState',                        # <-- Name of getter method
        '0'                                          # <-- Initial (raw) value
    ),

    'VEH_SPEED': (
        'float',
        'getVehicleSpeed',
        '0'
    ),

    'PRND_STAT': (
        'v1_0::acme::SocketCan::PrndlState',
        'getPrndlState',
        '0'
    ),
}

Signal = namedtuple(
    'Signal',
    'name id length offset endianess type getter initval'
)

def parse_can_defs():
    import os.path
    from lxml import etree
    kcdfile = os.path.join(os.path.dirname(__file__), 'can-db.kcd.bz2')
    with bz2.BZ2File(kcdfile) as f:
        tree = etree.parse(f)

    signals = []
    ns = dict(kcd="http://kayak.2codeornot2code.org/1.0")

    for name in SIGNALS.keys():
        elem = tree.xpath("//kcd:Signal[@name='{}']".format(name), namespaces=ns)[0]
        s = Signal(
                name,
                int(elem.getparent().attrib['id'], 16),
                elem.attrib['length'],
                elem.attrib['offset'],
                elem.attrib.get('endianess', 'little'),
                SIGNALS[name][0],
                SIGNALS[name][1],
                SIGNALS[name][2],
            )
        signals.append(s)
    return signals

if __name__ == '__main__':
    import sys
    sys.exit(main())

La "sintassi alternativa" in questo caso è la pura struttura dati Python SIGNALS , che definisce il sottoinsieme di segnali CAN dal file kcd a cui l'applicazione si preoccupa. Lo script analizza il file kcd e scorre i nomi dei segnali, popolando i campi id , length e offset per ciascuno. Lo script di generazione del codice inserito in Datum.h utilizza i dati restituiti per scrivere il codice. Se il mio capo mi informa che un altro segnale CAN è diventato improvvisamente 'interessante', aggiungo semplicemente un'altra voce al dizionario SIGNALS in socketcan/generator.py e rieseguo cog.py .

Potrebbero esserci altri modi idiomatici per farlo in C # (io principalmente codice in C ++ e Python), ma l'idea di base di dividere la logica in un file separato con cui è più facile lavorare e generare codice da quella rappresentazione alternativa , può essere utile. Il mio esempio è piuttosto complesso e ho dovuto tralasciare alcuni dettagli importanti, ma l'approccio generale è fondamentalmente semplice : trovare un modo per trasformare la codifica manuale, soggetta a errori, di logica insidiosa in dati automatizzati, -drive process.

    
risposta data 23.03.2017 - 22:46
fonte
1

Maybe i must use some pattern or tool like excel? How can i contain all rules in my head? :)

Il nucleo di questo è un problema di modellazione concettuale; essere in grado di catturare le regole (o almeno i metadati delle regole) in una struttura dati come una tabella può fare molto per semplificare la soluzione.

Il primo passo è cercare di esaminare le singole aree del modello concettuale in cui esiste la complessità; per esempio

  • Elaborazione degli aggiornamenti a intervalli regolari, come le simulazioni in tempo reale di un modello che è naturalmente complesso nel mondo reale (ad esempio, auto a guida automatica che obbediscono alle regole della strada)
  • Decisioni prese al punto di ricevere input da una fonte esterna (ad esempio prevedendo tendenze di mercato basate su grandi quantità di dati finanziari).
  • Interazioni complesse tra le tue entità (ad esempio alcuni agenti controllati A.I. in un gioco per computer)
  • Necessità di elaborare e interpretare dati strutturati in modo inadeguato come il linguaggio naturale (ad esempio dispositivi per l'assistente personale che accettano istruzioni vocali umane)

Considera se le strutture dati sono più appropriate del codice per la gestione della complessità del tuo modello di dominio. Ad esempio, i processori del linguaggio naturale potrebbero utilizzare modelli di linguaggio statistico o strutture ad albero per decidere le relazioni tra le parole. D'altro canto, le transazioni sui mercati finanziari utilizzate per individuare le tendenze sono spesso rappresentate in strutture relazionali.   Potresti decidere di aver bisogno di un certo numero di strutture diverse per modellare parti diverse del tuo dominio.

Devi fare un giudizio su quanto del tuo modello concettuale dovrebbe essere definito usando le strutture dati rispetto a quelle che dovrebbero essere definite come algoritmi. Non ci sono regole rigide su dove tracciare la linea; sebbene consideri che i dati complessi sono spesso più difficili da eseguire per il debug rispetto al codice complesso.

La linea tra i dati e il codice è sfocata; una struttura dati può essere progettata in modo tale che rappresenti tutta una serie di diramazioni e decisioni che potresti altrimenti rappresentare in codice, ma se così facendo risulti in te un lungo periodo di tempo cercando di eseguire il debug di tali dati allora non hai davvero guadagnato qualcosa

Forse il modo migliore per avvicinarsi a questo non è tentare di progettare un sistema complesso enorme in prima pagina, ma semplicemente scrivere qualcosa che funzioni e risolvi un problema; quando lavori con una serie complessa di requisiti, i pattern sia nei tuoi dati che nel tuo codice potrebbero non essere del tutto chiari fino a quando non hai iniziato a scrivere qualcosa che funzioni effettivamente; quando i modelli iniziano ad emergere, dovrebbe essere più facile vedere dove tracciare la linea tra code-vs-data.

    
risposta data 23.03.2017 - 23:05
fonte

Leggi altre domande sui tag