Come organizzare la business logic che si occupa di oggetti diversi ma correlati

3

L'attività

Il software che sto scrivendo funziona sui seguenti tipi di oggetto:

  • Agenti
  • Chiamate
  • CallQueues

Questi oggetti possono essere collegati insieme e ognuno di essi contiene alcune informazioni aggiuntive. Ad esempio, una chiamata contiene il numero del chiamante, un agente contiene il suo nome e così via.

Questo modello dovrebbe reagire agli eventi esterni consegnati attraverso una coda di messaggi. La maggior parte degli eventi è simile alla seguente:

  • un agente è entrato in un CallQueue;
  • un agente ha lasciato un CallQueue;
  • una chiamata è stata aggiunta a un CallQueue;
  • una chiamata ha lasciato un CallQueue;
  • un agente ha risposto a una chiamata;
  • un agente ha completato una chiamata;
  • ecc.

E così via. Fondamentalmente, la maggior parte degli eventi innescano la creazione e la rimozione di collegamenti tra gli oggetti e contengono anche informazioni aggiuntive. Alcuni eventi innescano una logica di business aggiuntiva, che è al di fuori dello scopo di questa domanda.

Gli aggiornamenti di agenti, chiamate e CallQueues devono essere osservati dall'esterno per scenari come questi:

  • distribuzione degli aggiornamenti dello stato dell'applicazione ai client attraverso i flussi di eventi (ad esempio tramite WebSockets);
  • persistenti aggiornamenti a un database;
  • ecc.

Un aggiornamento è un evento che viene generato quando i dati dell'oggetto sono cambiati o è stato creato o rimosso un collegamento a un altro oggetto.

La domanda

Quali sono le migliori pratiche per organizzare la logica aziendale? Opzioni che ho:

  1. Crea un componente separato per la gestione di ogni tipo di oggetto, e. g. AgentsRegistry , CallsRegistry e CallQueuesRegistry . Quando, ad esempio, si verifica un evento agente-collegato alla coda di chiamata, acquisirlo in CallQueuesRegistry , creare un collegamento e notificare AgentsRegistry , in modo che entrambi eseguano la propria logica aziendale. Esponi i flussi di eventi di aggiornamento da ciascun componente al mondo esterno, in modo che i gestori di persistenza e WebSockets possano collegarsi a esso e reagire.

  2. Crea un singolo componente ApplicationState per la gestione di tutti i tipi di eventi. Elabora collegamenti aggiornamenti internamente. Prova a separare la logica di business in metodi privati. Esporre un singolo flusso di eventi di aggiornamento dal componente al mondo esterno.

  3. Un mix dei due precedenti: nascondi i componenti separati dietro un oggetto ApplicationState , che dovrebbe reagire agli eventi e fungere da conduttore per la logica di business specifica del tipo.

  4. Metti la logica aziendale in Agenti, Chiamate e CallQueues - Non riesco a immaginare questo tipo di approccio quando entra in gioco la Dependency Injection e devono essere utilizzati componenti aggiuntivi. Inoltre, non è chiaro per me quale oggetto debba reagire a un evento di tipo agent-has-answer-a-call: l'agente o il call.

Quale opzione è preffered e perché? Quali altre opzioni esistono?

EDIT: dividi la domanda in due , ribattezzato Queues to CallQueues.

EDIT 2: Chiarificazione: un CallQueue è generalmente un elenco di chiamate in attesa di essere elaborate da un Agent che è vincolato a quel particolare CallQueue . È una rappresentazione virtuale di un queue reale in un'applicazione esterna e non ha nulla a che fare con una struttura di dati queue , perché la logica FIFO è gestita in quell'app esterna. La mia app riceve solo notifiche su una determinata chiamata che entra nella coda, lasciandola (cosa che può accadere in modo casuale) o raggiungendo un Agent libero e connettendosi con essa. Più CallQueues esiste.

Non esiste una coda di Agents in attesa di Call , almeno nell'ambito di questa app. Per quanto ci riguarda, una Agent pseudo-casuale si connette a un% pseudo-casuale% di% della Call .

    
posta G. Kashtanov 31.05.2018 - 15:05
fonte

1 risposta

2

responsabilità

Conosco diversi modi per farlo, ma solo un modo che porta a un codice che sembrava "pulito" e non mi ha fatto perdere la visione d'insieme e trasformo almeno parzialmente in spaghetti. Certamente non voglio implicare che questo sia " il modo corretto". Ciò è strongmente influenzato dal libro "Codice pulito" di Robert C. Martin, tra gli altri.

"True" OOP

Per quanto ne so, ci sono 2 scuole di pensiero riguardo l'OOP. Per la maggior parte delle persone, si riduce a "un oggetto è una classe".

Tuttavia, ho trovato più utile l'altra scuola di pensiero: "un oggetto è qualcosa che fa qualcosa". Se stai lavorando con le classi, ogni classe dovrebbe essere una delle due cose: un " data-holder " o un oggetto . I titolari di dati conservano i dati (duh), gli oggetti fanno cose. Ciò significa che non significa che un detentore di dati non può avere metodi! Pensa a list : A list non fa veramente do , ma ha dei metodi, per far rispettare il modo in cui tiene i dati. Per il resto di questa risposta, assumeremo questa definizione di OOP.

Titolari di dati

Se non trascuro qualcosa, tutte le cose che hai menzionato sono titolari di dati. Agents , Calls , CallQueues , AgentsRegistries - ognuno di essi è una raccolta di fatti. Certo, al di fuori del tuo programma un agente è qualcuno che fa qualcosa, ma all'interno del tuo programma, un Agent è per es. una stringa EmployeeId , una stringa Name e una int NumberOfCalls .

Anche i possessori di dati che non fanno non significano che non contengano logica aziendale! Se la tua classe Agent ha un costruttore che richiede EmployeeId , Name e NumberOfCalls , hai incapsulato correttamente la regola aziendale che ogni agente ha. Se non puoi modificare EmployeeId dopo aver creato una classe Agent , questa è un'altra regola aziendale incapsulata. Queste sono cose molto semplici, ma questo significa che è molto importante non sbagliare - e in questo modo, assicurati che sia impossibile sbagliare.

Oggetti

Gli oggetti fanno qualcosa. Se vuoi pulire oggetti, possiamo cambiarlo in "Oggetti fanno una cosa ".

Quello che hai citato sotto l'opzione 1 della tua business logic sarebbe il lavoro di un oggetto. Ad esempio, puoi chiamare questo oggetto " CallRegisterer ", con un metodo pubblico RegisterCall(Call, Agent) . Questo è tutto ciò che dobbiamo sapere dall'esterno, questo oggetto registra le chiamate e, per farlo, ha bisogno di un Call e un Agent .

All'interno, questo oggetto avrebbe riferimenti a AgentsRegistry e CallQueuesRegistry in modo che potesse aggiornarli di conseguenza. Se la logica per l'aggiornamento di AgentRegistry diventa troppo complessa, CallRegisterer può avere solo un altro oggetto AgentsRegistryUpdater che è responsabile di quella parte. Se in seguito, tali chiamate devono essere registrate su un file, puoi aggiungere un oggetto CallToFileLogger che lo fa.

PS Ho già detto che le cose che menzioni sono per lo più titolari di dati. Per le cose da fare, vedo solo verbi molto astratti come "esegui la loro logica di business" di "Esponi i flussi di eventi di aggiornamento", quindi questo esempio potrebbe non essere perfetto.

Titolari di dati vs oggetti - riepilogo

Ora, perché questa divisione è così buona? Perché ora cambiando quella classe cambia solo una cosa ( il modo in cui una chiamata è registrata nell'esempio). Se stai cambiando un oggetto, cambi solo il modo in cui viene eseguita una determinata azione; se si modifica un titolare di dati, si modificano solo quali dati vengono conservati e / o in che modo vengono conservati.

Cambiare i dati potrebbe significare che anche il modo in cui qualcosa è fatto deve cambiare, ma quel cambiamento non avverrà finché non lo fai succedere cambiando l'oggetto responsabile.

Iniezione di dipendenza

È positivo che tu abbia menzionato DI, perché questo è notevolmente più semplice. Se diversi oggetti devono lavorare su AgentQueue , potrebbe essere una seccatura per ottenere la stessa istanza a tutti loro.

Se implementi i pattern di osservatore sulle tue classi XYRegistry , far funzionare tutto questo è solo questione di connettere i tuoi oggetti recitazione a quegli eventi osservabili. Questa è un'azione, quindi per tenerla pulita ci dovrebbe essere un altro oggetto che lo faccia; Mi piace andare con le classi chiamate XYInitializer . Il tuo framework DI potrebbe avere un modo "integrato" per chiamare tali classi di inizializzazione.

    
risposta data 01.06.2018 - 16:43
fonte