2015-04-20 7 views
14

Sto usando RestTemplate come il mio HttpClient per eseguire l'URL e il server restituirà una stringa json come risposta. Il cliente chiamerà questa libreria passando l'oggetto DataKey che contiene userId.Come seguire il principio di Responsabilità Unica nel mio esecutore HttpClient?

  • Utilizzando la data userId, io scoprire quali sono le macchine che posso colpire per ottenere i dati e quindi memorizzare quelle macchine in un LinkedList, in modo che io possa eseguire in modo sequenziale.
  • Dopodiché controllerò se il primo hostname è in block list o no. Se non è presente nell'elenco di blocco, creerò un URL con il primo nome host nell'elenco e lo eseguirò e se la risposta ha esito positivo, restituire la risposta. Ma diciamo che se quel primo hostname è nella block list, allora cercherò di ottenere il secondo hostname nella lista e fare l'url ed eseguirlo, quindi in pratica, prima trovare il nome host che non è nella block list prima di fare l'URL.
  • Ora, diciamo se abbiamo selezionato il primo hostname che non era nella lista dei blocchi ed eseguito l'URL e in qualche modo il server era inattivo o non rispondeva, quindi eseguirò il secondo hostname nella lista e continuerò a farlo fino ad ottenere un risposta di successo. Ma assicurati che non fossero nella lista dei blocchi, quindi dobbiamo seguire il punto precedente.
  • Se tutti i server sono inattivi o in elenco di blocco, quindi posso semplicemente accedere e restituire l'errore che il servizio non è disponibile.

Qui di seguito è la mia classe DataClient che sarà chiamato dal cliente e passeranno DataKey oggetto da getData metodo.

public class DataClient implements Client { 

    private RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); 
    private ExecutorService service = Executors.newFixedThreadPool(15); 

    public Future<DataResponse> getData(DataKey key) { 
     DataExecutorTask task = new DataExecutorTask(key, restTemplate); 
     Future<DataResponse> future = service.submit(task); 

     return future; 
    } 
} 

Qui di seguito è la mia classe DataExecutorTask:

public class DataExecutorTask implements Callable<DataResponse> { 

    private DataKey key; 
    private RestTemplate restTemplate; 

    public DataExecutorTask(DataKey key, RestTemplate restTemplate) { 
     this.restTemplate = restTemplate; 
     this.key = key; 
    } 

    @Override 
    public DataResponse call() { 
     DataResponse dataResponse = null; 
     ResponseEntity<String> response = null; 

     MappingsHolder mappings = ShardMappings.getMappings(key.getTypeOfFlow()); 

     // given a userId, find all the hostnames 
     // it can also have four hostname or one hostname or six hostname as well in the list 
     List<String> hostnames = mappings.getListOfHostnames(key.getUserId()); 

     for (String hostname : hostnames) { 
      // If host name is null or host name is in local block list, skip sending request to this host 
      if (ClientUtils.isEmpty(hostname) || ShardMappings.isBlockHost(hostname)) { 
       continue; 
      } 
      try { 
       String url = generateURL(hostname); 
       response = restTemplate.exchange(url, HttpMethod.GET, key.getEntity(), String.class); 

       if (response.getStatusCode() == HttpStatus.NO_CONTENT) { 
        dataResponse = new DataResponse(response.getBody(), DataErrorEnum.NO_CONTENT, 
          DataStatusEnum.SUCCESS); 
       } else { 
        dataResponse = new DataResponse(response.getBody(), DataErrorEnum.OK, 
          DataStatusEnum.SUCCESS); 
       } 

       break; 
       // below codes are duplicated looks like 
      } catch (HttpClientErrorException ex) { 
       HttpStatusCodeException httpException = (HttpStatusCodeException) ex; 
       DataErrorEnum error = DataErrorEnum.getErrorEnumByException(httpException); 
       String errorMessage = httpException.getResponseBodyAsString(); 
       dataResponse = new DataResponse(errorMessage, error, DataStatusEnum.ERROR); 

       return dataResponse; 
      } catch (HttpServerErrorException ex) { 
       HttpStatusCodeException httpException = (HttpStatusCodeException) ex; 
       DataErrorEnum error = DataErrorEnum.getErrorEnumByException(httpException); 
       String errorMessage = httpException.getResponseBodyAsString(); 
       dataResponse = new DataResponse(errorMessage, error, DataStatusEnum.ERROR); 

       return dataResponse; 
      } catch (RestClientException ex) { 
       // if it comes here, then it means some of the servers are down so adding it into block list 
       ShardMappings.blockHost(hostname); 
      } 
     } 

     if (ClientUtils.isEmpty(hostnames)) { 
      dataResponse = new DataResponse(null, DataErrorEnum.PERT_ERROR, DataStatusEnum.ERROR); 
     } else if (response == null) { // either all the servers are down or all the servers were in block list 
      dataResponse = new DataResponse(null, DataErrorEnum.SERVICE_UNAVAILABLE, DataStatusEnum.ERROR); 
     } 

     return dataResponse; 
    } 
} 

mio elenco di blocco mantiene-on sempre aggiornato da un altro thread in background ogni 1 minuto. Se qualsiasi server è giù e non risponde, poi ho bisogno di bloccare quel server utilizzando questo -

ShardMappings.blockHost(hostname); 

E per verificare se qualsiasi server è nella lista di blocco o no, io uso questo -

ShardMappings.isBlockHost(hostname); 

Sto restituendo SERVICE_UNAVAILABLE se i server sono inattivi o in elenco di blocco, sulla base del controllo response == null, non sono sicuro se sia un approccio corretto oppure no.

Non sto seguente principio di responsabilità singola qui Credo che a tutti. Qualcuno può fornire un esempio su quale sia il modo migliore di utilizzare il principio SRP qui.

Dopo averci pensato molto, sono stato in grado di estrarre classe padroni di casa come indicato di seguito, ma non è sicuro qual è il modo migliore per utilizzare questo nella mia classe di sopraDataExecutorTask.

public class Hosts { 

    private final LinkedList<String> hostsnames = new LinkedList<String>(); 

    public Hosts(final List<String> hosts) { 
     checkNotNull(hosts, "hosts cannot be null"); 
     this.hostsnames.addAll(hosts); 
    } 

    public Optional<String> getNextAvailableHostname() { 
     while (!hostsnames.isEmpty()) { 
      String firstHostname = hostsnames.removeFirst(); 
      if (!ClientUtils.isEmpty(firstHostname) && !ShardMappings.isBlockHost(firstHostname)) { 
       return Optional.of(firstHostname); 
      } 
     } 
     return Optional.absent(); 
    } 

    public boolean isEmpty() { 
     return hostsnames.isEmpty(); 
    } 
} 
+1

Offtopic: Qual è il vantaggio dell'utilizzo di modelli in primavera? –

+1

@PantaRhei RestTemplate è un comodo wrapper su HttpClient. Semplifica e astrae solo alcuni dei più comuni casi d'uso e codice. – john

risposta

6

La vostra preoccupazione è valido. In primo luogo, vediamo cosa l'esecutore dati originali fare:

First, it is getting list of hostnames 
Next, it loops through every hostnames that do the following things: 
    It checks whether the hostname is valid to send request. 
    If not valid: skip. 
    Else continue. 
     Generate the URL based on hostname 
     Send the request 
     Translate the request response to domain response 
     Handle exceptions 
If the hostnames is empty, generate an empty response 
Return response 

Ora, che cosa possiamo fare per seguire SRP? Come posso vedere, possiamo raggruppare queste operazioni in alcuni gruppi. Quello che posso vedere è, queste operazioni può essere suddiviso in:

HostnameValidator:  checks whether the hostname is valid to send request 
-------------- 
HostnameRequestSender: Generate the URL 
          Send the request 
-------------- 
HttpToDataResponse:  Translate the request response to domain response 
-------------- 
HostnameExceptionHandler: Handle exceptions 

Cioè, un approccio per disaccoppiare le operazioni e di seguire SRP. C'è anche altro approccio, ad esempio per semplificare le operazioni di:

First, it is getting list of hostnames 
If the hostnames is empty, generate an empty response 
Next, it loops through every hostnames that do the following things: 
    It checks whether the hostname is valid to send request 
    If not valid: remove hostname 
    Else: Generate the URL based on hostname 
Next, it loops through every valid hostnames that do the following things: 
    Send the request 
    Translate the request response to domain response 
    Handle exceptions 
Return response 

allora può anche essere diviso in:

HostnameValidator:  checks whether the hostname is valid to send request 
-------------- 
ValidHostnameData:  Getting list of hostnames 
          Loops through every hostnames that do the following things: 
           Checks whether the hostname is valid to send request 
           If not valid: remove hostname 
           Else: Generate the URL based on hostname 
-------------- 
HostnameRequestSender: Send the request 
-------------- 
HttpToDataResponse:  Translate the request response to domain response 
-------------- 
HostnameExceptionHandler: Handle exceptions 

Naturalmente ci sono anche altri modi per farlo. E lascio i dettagli di implementazione vuoti perché ci sono molti modi per implementarlo.

Problemi correlati