Errori di microservizio a monte

0

Come per l'architettura dei microservizi, dovrebbe esserci una base di codice per microservizio senza alcuna API condivisa. Ciò impone un alto livello di disaccoppiamento e ogni microservizio è limitato all'interno del proprio contesto.

Ma questi microservizi, nel tempo, comunicheranno tra loro. Il servizio A parla con il servizio B e il servizio B parla con il servizio C, ecc ...

Dato questo scenario:

  • - > significa richiesta

Cliente - > Servizio A - > Servizio B

Considera che il servizio A è un servizio account e il servizio B è un servizio utente. Affinché il servizio A possa creare un account, richiede il servizio B se l'utente esiste.

Nel caso in cui il Servizio B non riesca (nessun utente ha trovato, errore di sistema), in che modo il Servizio A dovrebbe esporre questo errore al Cliente? Potevo solo pensare a due scelte:

Soluzione 1: esporre l'errore così com'è, utilizzare il codice di errore del servizio B

Pro: facile da fare. Catturare e rilanciare l'errore generato dal servizio B

Contro: se ogni microservizio avrebbe bisogno della propria base di codice, ciò significa che un microservizio ha la propria definizione di errori. La soluzione 1 rompe i principi dell'utilizzo dei microservizi poiché ora sembra che il servizio A conosca i codici di errore del servizio B.

Soluzione 2: cattura l'errore nel servizio A e genera un errore molto più specifico del servizio

Pro: questo produce un errore molto più chiaro per il client

Contro: il Service A dovrà rilevare e aspettarsi che il Server B sia in grado di restituire un sacco di errori, aggiungendo l'accoppiamento tra il Servizio A e B.

Per come la vedo io, entrambe le soluzioni accoppiano ulteriormente ogni microservizio.

    
posta mpmp 16.06.2017 - 23:32
fonte

1 risposta

1

Le eccezioni dovrebbero essere trattate come i modelli di dominio. Ogni servizio funziona con i propri modelli di dominio e dovrebbe avere anche il proprio insieme di modelli di eccezione. Quando comunichi con sistemi esterni, il servizio dovrebbe convertire le eccezioni esterne alle eccezioni del dominio il prima possibile. Fondamentalmente sto dicendo di andare con la soluzione # 2.

Consideriamo la comunicazione dal servizio A - > B. Il servizio A dovrebbe prima di tutto avere un'interfaccia definita per disaccoppiare la logica aziendale dall'implementazione delle richieste a B. Nell'esempio A è un servizio account e B è un servizio utente. Chiamiamo quindi l'interfaccia UserService . Questa interfaccia avrebbe una serie di eccezioni (idealmente) controllate dal compilatore.

interface UserService
    def getUser(id): User throws UserNotFoundException, UserServiceException

È necessario implementare il client HTTP per il servizio B in modo che qualsiasi servizio che deve dipendere dal servizio B importi il client HTTP comune. Le risposte di errore dalle richieste al servizio B saranno definite in questo componente client HTTP. In questo modo vengono definiti solo una volta.

class BHttpClient
    def getUserById(id) = 
        response = http.get("/users/${id}").send
        if (response.status == 404) throw new UnknownUserException
        else if (response.status == 500) throw new InternalServerException
        else return json.parse[User](response.content)

L'implementazione di UserService , HttpUserService userà quel client HTTP per comunicare con B, dovrebbe intercettare HTTP e trasportare eccezioni dal client e avvolgerle nell'eccezione "dominio" appropriata.

class HttpUserService(client: BHttpClient) implements UserService
    def getUser(id) = 
        try {
            client.getUserById(id)
        } catch {
            case e: UnknownUserException => throw new UserNotFoundException(e)
            case e: InternalServerException => throw new UserServiceException(e)
        }

Cons: Service A will need to catch and expect that Server B is capable of returning a bunch of errors, adding coupling between Service A and B.

Il servizio A rileva gli errori dal client http in HttpUserService e li avvolge in errori significativi per il servizio A. La logica di business nel servizio A è disaccoppiata dal servizio B attraverso l'interfaccia UserService . HttpUserService è accoppiato a BHttpClient , ma disaccoppiato dal servizio B perché puoi simulare il servizio B a livello di trasporto.

Anche se si sceglie di utilizzare un'architettura diversa come @Laiv nei commenti, si vorrà comunque dissociarsi dal messaggio e dagli eventi ricevuti convertendo i modelli di messaggio e le eccezioni in eccezioni di dominio in ciascun servizio. Non sono d'accordo con @Laiv, che è asettico e tagliato come un'architettura di messaggi asincrona o si potrebbe anche implementare un monolite. Ci sono ancora grandi guadagni che possono essere fatti da un'architettura sincrona e distribuita orientata ai servizi come hai descritto. Il primo e più difficile passo per ottenere la giusta architettura è quello di disaccoppiare i componenti. Dividendo precocemente i microservizi, è possibile adottare più facilmente un approccio asincrono in un secondo momento, se necessario.

    
risposta data 17.06.2017 - 03:11
fonte

Leggi altre domande sui tag