AntonioGanci

Il blog di Antonio Ganci
posts - 201, comments - 420, trackbacks - 31

Architettura di un software, un caso di un'applicazione reale con dubbi sollevati sulla soluzione intrapresa.

Sto sviluppando un servizio di windows che comunica con alcuni client tramite i socket. L'architettura dell'applicativo include una classe Client con la responabilità di ricevere i dati dai client remoti connessi al server. La connessione viene gestita tramite la classe SocketChannel che implementa l'interfaccia IChannel le quali fanno parte del layer di comunicazione.

Un piccolo diagramma UML semplificato per schematizzare le classi e le interfacce coinvolte:

Message Processing Class Diagram

Ovviamente ho volutamente semplificato la reale architettura, infatti, manca il log, la gestione degli errori, ecc.

Una versione semplificata (ma non lontana dalla realtà) del metodo Receive:

        public void Receive()

        {

            while(_channel.IsConnected)

            {

                string rawData = _channel.Receive();

                IMessage[] messages = _messageParser.Parse(rawData);

                _messageProcessor.ProcessMessages(messages);

            }

        }

Inizialmente il client instanziava internamente le classi concrete SocketChannel che implementa l'interfaccia IChannel, la classe MessageParser che implementa l'interfaccia IMessageParser ed infine la classe MessageProcessor che implementa l'interfaccia IMessaggeProcessor; poi ho applicato la strategia di design Dependency Injection ed ho estratto le tre interfacce spostando la responsabilità della loro creazione fuori dalla classe Client e all'interno della classe Server.

Vediamo come interagisce il Server con i clients:

Server Class Diagram

Il metodo OnClientConnected:

        public void OnClientConnected(IChannel channel)

        {

            Client client = new Client(channel, new MessageParser(), _messageProcessor);

            _clients.Add(client);

        }

Come si può vedere ogni volta che un client si connette viene creata una nuova istanza della classe MessageParser, mentre viene riutilizzato lo stesso MessageProcessor (il field _messageProcessor).

La domanda che ci si può porre a questo punto è perchè non viene usato la stessa istanza del parser per tutti i client, ma ne viene creata una nuova ogni volta?

La risposta è che il parser ha la responsabilità di comporre i messaggi che arrivano spezzati in più parti. Mi spiego meglio: i socket, in questo caso utilizzando il protocollo TCP/IP, non garantiscono che un messaggio spedito venga ricevuto in un'unica volta (maggiori dettagli in questo nel post General things to take into consideration while doing socket programming), ma possono essere necessarie più ricezioni, quindi è compito del ricevente gestire questa situazione.

Purtroppo il formato dei messaggi non prevede la lunghezza del messaggio (si basa solo su dei tag di inizio e fine) e quindi non mi è possibile fare questa composizione a livello di layer di comunicazione; questa responsabilità è stata data al parser, il quale solo una volta individuato un messaggio completo si accorge di aver finito la ricezione.

Per meglio chiarire supponiamo che il client spedisca il seguente messaggio:

<start>Messaggio di prova</end>

e il server lo riceva spezzato in due: <start>Messagg e poi separatamente io di prova</end>

La classe in grado di comprendere che quando ricevo la seconda parte ho terminato di ricevere il messaggio è il MessageParser. Quindi questo è il motivo per cui ne devo instanziare uno diverso per ogni client perchè il server ha un canale di comunicazione diverso per ogni client e i messaggi devono essere ricomposti separatamente.

Il problema è che dall'architettura che ho evidenziato questo non è chiaro, un'altra persona che prende in mano il progetto potrebbe togliere la new MessageParser()  dal metodo OnClientConnected e sostituirla con un field. Un'altra strada è quella di spostare questa responsabilità al client, ma in questo caso gli aggiungerei troppo carico oltre quello che ha e dipenderebbe da un dettaglio che è più da layer di comunicazione.

Mi piacerebbe sentire il vostro parere su questo problema di architettura.

Print | posted on lunedì 4 dicembre 2006 20:02 |

Feedback

Gravatar

# re: Architettura di un software, un caso di un'applicazione reale con dubbi sollevati sulla soluzione intrapresa.

Ciao,
io sto realizzando qualcosa di molto simile (per non dire identico) al tuo progetto.

Io ho spezzato le responsabilità del tuo Client: in pratica ho una instanza di un tipo Reader per ogni socket, la cui responsabilità è quella di ottenere il messaggio compreso tra i tag iniziali e finali, che passa poi il messaggio ripulito a varie instanze di un tipo Handler, che si occupa di processare il messaggio. Per contenere l'istanza della socket utilizzo un'altra classe, che poi registro in un singleton per avere un qualche tipo di gestione delle sessioni.

Non credo di aver risposto alla tua domanda, ma almeno sono contento di non essere l'unico che lavora a livello trasporto!

04/12/2006 20:58 | Stefano
Gravatar

# re: Architettura di un software, un caso di un'applicazione reale con dubbi sollevati sulla soluzione intrapresa.

Non mischiamo l'olio con l'aceto...
Quello NON è un diagramma UML... :-)
04/12/2006 21:14 | Lorenzo Barbieri
Gravatar

# re: Architettura di un software, un caso di un'applicazione reale con dubbi sollevati sulla soluzione intrapresa.

Forse questo argomento troverebbe migliore risposta nel forum del guisa (http://www.guisa.it).

Gian Maria.
04/12/2006 21:17 | Gian Maria
Gravatar

# re: Architettura di un software, un caso di un'applicazione reale con dubbi sollevati sulla soluzione intrapresa.

Esatto, solo che il mio MessageParser non è incluso in un Client. Io non ho un Client, ho una specie di sessione se voglio mantenere dello stato tra più chiamate, proprio come per le sessioni HTTP nelle applicazioni web.
Comunque la tua soluzione mi sembra molto pulita; potresti istanziare internamente al tuo client il MessageParser ed anche il MessageProcessor, senza farteli passare dal server, magari con delle factory.
In fondo potrebbe essere responsabilità del client come parserizzare e processare il messaggio, cioè potrebbero esserci client diversi che hanno logiche diverse (almeno questo è il mio caso).
Ciao
05/12/2006 11:53 | Stefano
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET