Architecting Service, Manager e Model per la concorrenza in un'applicazione web

2

Diciamo che ho un'applicazione di chat che gestisce stanze, utenti e messaggi. Sto costruendo questo come un'opportunità per praticare alcuni servizi / manager / web separazione e insegnare a me stesso una buona progettazione dell'applicazione.

Con le seguenti classi:

Modello:

public class Room {
    private final String id = UUID.randomUUID().toString();

    private boolean open = true;

    private List<Message> messages = new ArrayList<Message>();

    public void addMessage(Message m) {
        synchronized(messages) {
            messages.add(m);
        }
    }

    public void close() {
        this.open = false;
    }

    public List<Message> getMessages() {
        synchronized(messages) {
            return new ArrayList<>(messages);
        }
    }
}

Responsabile:

public interface RoomManager {
    public void add(Room r);
    public Room get(String id);
}

Servizio:

public class RoomService {
    public void postMessage(Message m, Room r) {
        if (!r.isOpen()) {
            throw new IllegalArgumentException("Room " + r + " is closed");
        }
        r.addMessage(m);
    }
}

La classe Message non è eccitante - un contenuto String e forse un timestamp.

Ciò di cui sto combattendo è come impaginare questo disegno e, si spera, rispondi ad alcune domande fastidiose che mi affliggono.

Supponendo che questo quadro sopra venga utilizzato in un servizio Web con più richieste in entrata:

  1. Una richiesta web per pubblicare un messaggio in una stanza avrà solo l'ID della stanza. La ricerca (traduzione da String roomId - > Room room ) si verifica a livello web? O il metodo di servizio dovrebbe essere modificato per accettare l' ID della Stanza invece della Stanza stessa?

  2. Esiste una condizione di competizione nel metodo postMessage() in cui lo stato della stanza potrebbe cambiare tra il controllo e la pubblicazione effettiva del messaggio. Il livello di servizio è il posto giusto per la sincronizzazione?

  3. Preferisco che il servizio non debba sapere che tipo di gestore ha a che fare: se si tratta di un database, una soluzione in memoria, ecc. Dove deve essere determinata la sicurezza del thread, sul modello, manager, o livello di servizio? Se aggiungo @Transactional al livello di servizio, non sarà di aiuto quando si ha a che fare con un gestore in memoria. Tuttavia, l'aggiunta di una sorta di sincronizzazione basata su oggetti al servizio potrebbe impedire inutilmente più query di database (che altrimenti non sono correlate, ovvero la pubblicazione di due messaggi in due stanze separate) dall'esecuzione in parallelo. Ovviamente se abbiamo a che fare con una query di database, la sicurezza del thread a livello di modello è un punto controverso, poiché a meno che non ci occupiamo di una cache locale o di un gestore di modelli in memoria, è probabile che ottenga due istanze separate per stesso oggetto.

Sembrano domande abbastanza fondamentali, ma ho difficoltà a trovare libri, tutorial, discorsi, ecc. che rispondano a questo tipo di domande di progettazione di livello superiore. Capisco la concorrenza e i vari strumenti (chiusure di rientro, blocchi sincronizzati, ecc.) Ma metterli in pratica in modo sano e gestibile sembra essere un'attività fai-da-te.

Sperando che qualcuno possa aiutare a rispondere alle domande di cui sopra, o fornire alcune risorse che potrebbero aiutarmi a rispondere da solo.

    
posta Craig Otis 04.11.2016 - 22:53
fonte

1 risposta

2

Non esiste una risposta corretta per nessuna delle domande che hai posto. Architettura, concorrenza, prestazioni, linguaggi di programmazione, ecc., È tutta una questione di compromessi. L'esperienza è la cosa che ti aiuta a riconoscerli e decidere su di loro. E l'esperienza viene dall'imparare, fare, imparare dal fare, commettere errori, imparare da quegli errori, quindi ripetere.

E per di più c'è anche il fattore "tempo". Il tempo cambia le cose, specialmente nel software. Crea ora un'applicazione ben progettata e in alcuni anni potrebbero essere necessarie alcune funzioni per le quali il design attuale non è delle migliori e deve essere modificato o completamente sostituito.

Tutti i libri, i discorsi, i tutorial, ecc. possono farti conoscere alcuni di questi problemi prima di doverli scoprire dolorosamente su di loro e darti una buona base e strumenti per iniziare a progettare le tue applicazioni.

Cercherò di aggiungere la mia visione delle cose alle domande che hai posto. Senza sapere di più sulla tua applicazione, oltre le poche righe di codice che hai aggiunto, queste sono solo opinioni:

(1) Questo dipende. Lo strato web dovrebbe sapere come recuperare una stanza in base all'ID? Perché questo deve accadere a livello web? Il tuo livello di servizio non dovrebbe sapere come gestire gli ID stanza e trasformarli in istanze di stanza? Non è il tuo RoomManager a farlo? Non è la parte RoomManager del tuo livello di servizio?

(2) Forse, forse no. Che ne dici se la stanza decide di accettare o rifiutare un messaggio?

public class Room {
    ....
    public void addMessage(Message m) {
        if (this.open) {
            synchronized (messages) {
                messages.add(m);
            }
        } else {
            throw new IllegalArgumentException("Room " + this + " is closed");
        }        
    }
    ....
}

Il tuo servizio quindi pubblica solo il messaggio.

Un'altra osservazione, come si chiude la stanza? Questo è soggetto a problemi di concorrenza? Il flag open deve essere volatile o no?

(3) Questo è un argomento ampio. Nella maggior parte delle applicazioni, i programmatori riescono a imbrogliare. Mantengono quindi controllori, servizi, archivi, ecc. Apolidi delegare i problemi di concorrenza al database che è ben attrezzato per gestirli. Quando non si dispone di un archivio transazionale di dati, devi gestire la concorrenza negli strati sopra. E ora compaiono i trade-off. È possibile scegliere di sincronizzare molto per garantire coerenza, o rilassare un po 'la sincronizzazione per ottenere prestazioni migliori. Uno è facile da fare ma potrebbe essere lento, l'altro è difficile da fare ma potrebbe essere più veloce. Qui, la lingua può aiutare fornendo buoni dati sicuri e performanti strutture . Potresti delegare parte della sincronizzazione a loro. Un altro modo potrebbe essere quello di rinunciare alla sincronizzazione tutti insieme nel livello di servizio e avere un livello di persistenza iniettabile. Per un database l'implementazione può usare @Transactional , per un archivio in memoria puoi usare java.util.concurrent classi, ecc.

Se stai facendo questo per l'apprendimento, allora decidi cosa pensi sia meglio ora, poi continua. Implementare una soluzione, quindi provare a cambiare un livello o estenderlo con alcune funzionalità. Dove si rompe? Dove sono le cose difficili da cambiare? ecc. Quindi prova a trovare soluzioni a questi problemi.

    
risposta data 05.11.2016 - 16:44
fonte