Come dovrebbero essere i parametri del metodo di servizio?

6

Sono solo uno sviluppatore junior ma in circa una mezza dozzina di progetti su cui ho lavorato c'è sempre questa situazione:

Metodi di servizio che prendono come oggetti grandi oggetti, ma spesso ne usano solo una piccola porzione. Lasciatemi provare a chiarire con un po 'di codice:

class WorkforceService {
    public void assignEmployeeToWorkplace(Workplace wp, Employee emp) {
        //this method just checks couple things that 
        //may prevent assigning that employee to that workplace
        //if everything's ok employee is assigned
    }
}

Difficoltà che ho con la situazione:

  • Se provo a testare questo metodo, ho bisogno di creare un posto di lavoro completo e un oggetto dipendente. Questi contengono molti altri membri e dati, che possono o non possono essere rilevanti per questo metodo.

  • Revisione. A un certo punto mi verrà chiesto di verificare queste chiamate di metodo. Questi grandi parametri rendono difficile automatizzare il controllo, non posso semplicemente serializzarli e scriverli in un log perché sono grandi e contengono dati irrilevanti per il metodo. Devo selezionare manualmente i dati che devo salvare.

Mi sto chiedendo questi:

1) Dovrebbe essere così?

2) In caso negativo, dov'è il problema? Questi oggetti sono inutilmente grandi e il metodo di servizio è ok, o è un errore del metodo di servizio e in qualche modo dovrebbe richiedere solo ciò di cui ha realmente bisogno?

    
posta Reek 18.11.2017 - 16:53
fonte

5 risposte

5

I metodi di servizio sono OK. E a volte il tuo modello di dominio risulta in oggetti di grandi dimensioni, che è anche OK. Ciò di cui potresti aver bisogno è qualche Segregazione dell'interfaccia , ed eventualmente un confine del contorno .

Gli oggetti Workplace e Employee hanno una superficie API grande che è per lo più irrilevante per questo metodo di servizio. Quindi, perché il servizio deve dipendere da questo?

Se questo è un problema (non sempre lo è), allora potresti voler introdurre delle interfacce che descrivono quale funzionalità è effettivamente richiesta dal tuo servizio. Ora il servizio non dipende più direttamente dal tuo modello di dominio.

Per connettere le classi del modello di dominio alle tue interfacce:

  • le classi del modello di dominio ereditano dalle interfacce di servizio o
  • presenta oggetti adattatore che traducono le chiamate attraverso l'interfaccia in chiamate alla classe del modello.

In questo caso, sembrerebbe dubbio che le classi del modello di dominio siano ereditate dalle interfacce di servizio. Ciò porterebbe al modello di dominio a seconda dei servizi, che è la direzione sbagliata per queste dipendenze. Più immediatamente, ciò significherebbe che tutte le classi di modelli erediterebbero dalle interfacce per ciascun servizio, il che rende le definizioni di classe poco maneggevoli e più difficili da estendere. Aumenta anche la probabilità di scontri sul nome. Quindi gli oggetti adattatori sarebbero preferibili.

Tali approcci sono più preziosi quando queste interfacce vengono utilizzate più volte, non solo per un singolo metodo. Altrimenti, si aggiunge molta complessità con tutte queste interfacce senza molto valore in cambio. Devi valutare la difficoltà di gestire tutte quelle interfacce contro la difficoltà di utilizzare oggetti di grandi dimensioni.

Potrebbe anche accadere che il servizio abbia bisogno di dati diversi perché fa parte di un dominio problematico diverso. L'idea del servizio di un Dipendente può differire da quella utilizzata nel resto del modello di dominio. Quindi, potrebbe essere meglio definire il proprio contesto limitato per quel servizio. Il servizio ha quindi il proprio modello di dominio. Ora devi tradurre tra i due modelli di dominio quando usi quel servizio, ma per il chiamante questo non è fondamentalmente diverso dal confezionamento di un oggetto in un adattatore.

In questo particolare esempio non sembra che il servizio dovrebbe essere in un contesto separato. Tuttavia, pensare a diversi contesti nella tua applicazione può essere un modo per determinare perché i tuoi oggetti attuali sono così ingombranti. Per esempio. per un software di gestione della forza lavoro, la rilevazione del tempo potrebbe essere una preoccupazione diversa dalle informazioni sul salario. Pertanto, se la tua classe Employee è intesa per il monitoraggio del tempo, non dovrebbe contenere anche il grado di retribuzione del dipendente, le informazioni di pagamento o il loro indirizzo di casa.

    
risposta data 18.11.2017 - 17:53
fonte
2

1) Sì, dovrebbe essere così. Il tuo esempio contiene già il motivo per cui penso di sì: "Questo metodo controlla solo le cose che potrebbero impedire l'assegnazione di quel dipendente a quel posto di lavoro". Questa logica di coerenza in questo momento ha bisogno di alcuni campi della classe Employee e Workplace. Ovviamente, il refactoring di quel metodo per usare i valori di campo semplici o le classi più compatte di EmployeeSubset o WorkplaceSubset darebbe lo stesso comportamento.

Ma ti perdi se le regole del dominio cambiano più tardi, quindi hanno bisogno di altri campi. Quindi affronti un enorme refactoring dei chiamanti per qualcosa che dovrebbe essere un cambiamento locale.

2) Focalizzato sul metodo assignEmployeeToWorkplace (), gli oggetti non sono inutilmente grandi. Sono solo i riferimenti passati al metodo e il chiamante sicuramente ha già le istanze Employee e Workplace per altri aspetti del suo compito.

E un concetto centrale di Domain Driven Design è quello di utilizzare gli oggetti dominio ovunque sia fattibile. E, per assegnare un dipendente a un posto di lavoro, i parametri naturali dovrebbero essere Dipendente e Posto di lavoro.

Per quanto riguarda le tue difficoltà:

Sì, li vedo, ma compromettere l'architettura principale per semplificare le attività secondarie non è una buona idea.

    
risposta data 18.11.2017 - 17:34
fonte
2

Sì, è normale. Tuttavia, questo non dovrebbe essere un problema.

Test

La sfida dei test di integrazione è assicurarsi che non ci sia un effetto inatteso . Se si verifica solo il comportamento previsto, sulle parti dell'oggetto che si prevede siano interessate, ci sono grandi probabilità che manchi qualche sorpresa. Perché sfortunatamente, anche se il tuo codice utilizza solo una parte di un oggetto, non puoi essere certo che questa parte non abbia impatto su altre parti o non sia influenzata dalle altre parti.

Per i test unitari la situazione è diversa. Soprattutto se stai usando TDD dove i casi di test sono implementati prima della funzionalità stessa. Qui puoi fare affidamento su alcune tecniche come raddoppio dei test per far fronte alla complessità non necessaria.

Controllo

Suppongo che intendi la registrazione dei dati (il controllo viene eseguito dai revisori e utilizzeranno i registri che hai implementato).

In effetti, o metti tutti gli oggetti nel tuo registro, oppure scegli i dati a mano. Ma quest'ultimo richiede un po 'di tempo. Una via d'uscita da questo pasticcio può essere l'utilizzo della registrazione multilivello:

  • La registrazione di base registrerà solo gli eventi di base nella tua funzione (voce, alcuni punti di controllo, risultato ed uscita), utilizzando solo una breve descrizione dell'oggetto (ad esempio, fornita da una funzione che registra l'id, e alcuni elementi minimalistici dati che verrebbero utilizzati in tutti i registri), solo per documentare che il processo ha avuto luogo con oggetti specifici.
  • Se il livello di registrazione è superiore, esegui il dump degli oggetti completi (nello stesso file di registro o in uno diverso).

Un altro approccio potrebbe essere quello di lavorare con un modello di strategia. L'idea è che i dati registrati insieme all'ID oggetto non siano più un breve riassunto o un dump completo come spiegato sopra, ma un elenco di campi personalizzato. Questo potrebbe essere implementato usando un modello di strategia. Quindi, invece di chiedersi cosa accedere ad ogni passaggio della propria funzione, definire la serie di campi da utilizzare nel registro per la propria funzione specifica e registrare sistematicamente tutti quei campi.

È un po 'più di lavoro. Ma invece di farlo per ogni passo del registro, lo scriverei solo una volta per la funzione.

    
risposta data 18.11.2017 - 17:48
fonte
1

Is it supposed to be like this?

Non proprio, no.

If not, where is the problem?

È una violazione del principio di segregazione dell'interfaccia , che è il I in SOLID .

Un approccio alternativo consiste nell'introdurre piccole interfacce di ruolo che descrivono il contratto che il cliente deve completare il suo lavoro.

Questo ti dà molte interfacce a grana fine che descrivono il comportamento, piuttosto che un'interfaccia a grani singoli che ti dà accesso a tutto e al lavello della cucina.

Upside: accettare un argomento di grana più fine rende più esplicito ciò di cui hai effettivamente bisogno. È più semplice implementare un'interfaccia a grana fine, il che significa che il riutilizzo è più semplice, come lo è la creazione dei duplicati di test che potresti desiderare.

Lato negativo: più interfacce a cui pensare. È necessario apportare ulteriori modifiche se in seguito si scopre che è necessario accedere ad altre parti dell'implementazione. Combinare le interfacce per un singolo argomento può essere goffo. È più lavoro. Sono anche più nomi e nominare le cose è difficile .

Are those objects unnecessarily big and service method is ok, or is it service method's fault and somehow it should request only whatever it actually needs?

Il problema principale è che il metodo di servizio non descrive precisamente ciò di cui ha bisogno. Può anche essere il caso in cui l'oggetto ha troppe responsabilità non correlate, rendendolo inutilmente grande.

Una delle motivazioni per è l'idea che "difficile da testare" spesso indica che ci sono opportunità per migliorare il design.

    
risposta data 18.11.2017 - 20:14
fonte
0

Lascia che lo metta in questo modo, è normale, ma non è OK. Molti progetti funzionano in questo modo, ma non molti si fermano a pensare a questo problema. I tuoi istinti sono corretti, è un odore.

Il problema è semplicemente un modello di dominio sbagliato. Sfortunatamente non è sempre semplice risolverlo.

Innanzitutto, il modello di dominio non è un modello di dati. In secondo luogo, il problema non è tecnico, quindi non si tratta di adattatori, interfacce, servizi, contesti o cose del genere.

Il Blue Book ha una descrizione abbastanza ampia di cosa dovrebbe essere DDD nelle prime 150-200 pagine. È un modello di dominio (ancora, non modello di dati), che si adatta al vero dominio del problema. Devi esercitare il tuo modello, parlare e pensare con esso, fino a quando la lingua si adatta.

Ok, come ti aiuta? Prova a parlare del problema che stai rappresentando nel tuo dominio! Puoi farlo? Ad esempio: "Un dipendente è assegnato a un posto di lavoro". Probabilmente non puoi, perché la parola "assegnare" manca nel tuo vocabolario. È in un "Servizio", che non fa realmente parte del dominio. Non puoi dire "WorkforceService" a un uomo d'affari, puoi? Quindi prova questo ad esempio:

public interface Workplace {
    void assign(Employee employee);
}

Fatto. Ok, ora lo fai al prossimo requisito. "Alcuni luoghi di lavoro accettano solo dipendenti senior impiegati prima di una determinata data." Il tuo modello può farlo? No, non può. I luoghi di lavoro possono decidere di controllare qualsiasi cosa, perché la logica del "compito" è presente, ma il "impiegato prima di una data data" non fa ancora parte del nostro vocabolario. Quindi aggiungiamolo al Dipendente:

public interface Employee {
    boolean wasEmployedBefore(Date date);
}

Ora posso dire che "un luogo di lavoro specifico può decidere di assegnare solo i dipendenti che sono stati impiegati prima di una data specifica", e tutto ha senso nel mio dominio.

E così via per tutti i requisiti. Ovviamente questo non è sempre facile, e non sempre inequivocabile. Il libro di Evans descrive abbastanza bene questo processo di scoperte e conoscenze. Questo dovrebbe essere al centro di DDD, non un modello di dati come sospetto tu abbia ora. Un problema come quello che stai avendo è una buona opportunità per saperne di più sul dominio e regolarlo di conseguenza.

    
risposta data 20.11.2017 - 11:24
fonte

Leggi altre domande sui tag