Manipulando erros e feedback ao executar operações em massa em uma arquitetura de várias camadas

StackOverflow https://stackoverflow.com/questions/1340062

Pergunta

Digamos que você tenha um método de lógica de negócios que possa executar alguma operação em vários objetos. Talvez você queira ligar para um serviço da Web que escolhe um número de loteria, uma vez para cada pessoa selecionada em uma lista. Em Java, o código pode parecer algo assim:

Set<Person> selectedPeople = ... // fetch list of people
for ( Person person : selectedPeople ) {
    String lotteryNumber = callLotteryNumberWebService( person );
    // ...
}

Observe que o serviço da Web do número de loteria pode ter efeitos colaterais, como gravar que a pessoa solicitou um número de loteria (talvez cobrando sua conta); portanto, mesmo que a chamada do serviço da Web falhe para uma pessoa, ela pode ter conseguido para outras pessoas. Essas informações (os números da loteria) precisarão ser alimentadas de volta a algum nível mais alto (a visualização).

Se esse fosse um caso em que ocorreu uma única operação, o método da lógica de negócios poderia retornar um único valor (digamos, o número da loteria) ou lançar uma exceção com qualquer detalhe da falha. Mas, para operações em massa, seria possível que algumas das operações tenham sucesso e algumas falhassem.

Parece um tipo de problema que ocorreria em muitos aplicativos e deve haver uma maneira limpa de lidar com isso. Então, qual é a melhor maneira de alimentar esse tipo de informação da camada lógica de negócios para outra camada em um aplicativo (como uma visualização), de preferência de maneira genérica que possa ser reutilizada para diferentes tipos de dados e operações?

Foi útil?

Solução

Se eu entender, você tem uma situação em que algumas solicitações podem passar e outras podem falhar. Não tenho certeza de onde você deseja que o erro volte, mas você pode ter um dos seguintes (ou uma variante ou uma combinação):

  • Uma lista de erros e os objetos de domínio afetados. Um objeto de domínio base ou algo com um ID persistente pode ser útil para reutilização. Por exemplo, uma coleção de erros referentes a objetos de domínio.
  • Você pode injetar (AOP, DI) no objeto de pessoa algum tipo de objeto de erro/mensagem. Por exemplo, if (pessoa. Erros) {...}
  • Você pode envolver a coleção de pessoas em uma mensagem com cabeçalho, corpo, informações de erro
  • Todos os seus objetos de domínio podem incluir uma coleta de erros acessível por meio de uma interface; ou a pessoa apóia a interface ihaserrors. Você pode fazer esse genérico e usar um objeto de erro básico que suporta avisos e validação e todo tipo de coisa.

Se você estiver em um sistema genuíno de várias camadas (em vez de em camadas), poderá ter uma arquitetura baseada em mensagens que possa acomodar facilmente algum tipo de mecanismo genérico de erro/aviso/validação. Os sistemas SOA/AJAX se prestam a isso.

Fico feliz em se aprofundar um pouco mais se você tiver algumas perguntas específicas.

Outras dicas

Esta pergunta destaca diferenças importantes entre os usos apropriados do manuseio de exceções, transações e a ideia fluxo de trabalho "compensação" que é o que o asker está tentando chegar, ao declarar corretamente:

Parece um tipo de problema que ocorreria em muitos aplicativos e deve haver uma maneira limpa de lidar com isso.

É um problema comum, primeiro alguns antecedentes sobre a abordagem transacional que você está tentando atualmente:

As transações de dados foram originalmente modeladas após contabilidade de entrada dupla-um único crédito e um correspondente débito tinha que ser gravado juntos ou não. À medida que as transações ficam maiores do que isso, elas se tornam cada vez mais problemáticas de implementar corretamente e mais difíceis de lidar com uma falha. Quando você começa a levar a idéia de uma única transação entre os limites do sistema, você provavelmente está se aproximando de errado. Isso pode ser feito, mas requer coordenadores de transações de latência complexos e necessariamente mais altas. Em uma certa escala, as transações são a mentalidade errada e a compensação começa a fazer muito mais sentido.

É aqui que você volta e vê o que a empresa realmente faz. Uma única transação grande provavelmente não é a maneira como as pessoas de negócios a veem. Geralmente eles vêem uma etapa concluída e, dependendo dos resultados subsequentes, podem ser necessárias ações diferentes para compensar. É aqui que a ideia de um fluxo de trabalho e compensação entra. Aqui está uma introdução a esses conceitos

Por exemplo, se você pedir um livro da Amazon, eles provavelmente não "bloqueiam" o registro enquanto estiver no seu carrinho de compras ou até usam transações rigorosas para determinar se o livro ainda está em estoque quando o pedido é confirmado. Eles o venderão de qualquer maneira e o enviarão quando puderem. Se eles não conseguiram colocá-lo em estoque dentro de algumas semanas, provavelmente enviarão um e-mail dizendo que eles estão tentando atender às suas necessidades, e você poderá continuar esperando que eles o coloquem em estoque, ou você pode cancelar seu pedido. Isso é chamado de compensação e é necessário em muitos processos de negócios do mundo real.

Finalmente, existe nada excepcional sobre qualquer isso. Espere que isso possa acontecer e use o fluxo de controle normal. Você não deve usar os recursos de manuseio de exceção do seu idioma aqui (boas regras para quando jogar uma exceção). Você também não deve confiar nos mecanismos específicos da ferramenta (WCF?) Para ver ou lidar com exceções que acontecem na implementação do serviço. A comunicação de falhas deve ser uma parte normal do seu contrato de dados (contratos de falha).

Infelizmente, por "maneira limpa de lidar com isso", não há bandeira para definir magicamente cuidar disso, você deve continuar a decompor o problema e lidar com todas as peças resultantes. Espero que esses conceitos o conectem ao que outras pessoas fizeram ao lidar com esse problema.

Resumo:

  • Seu problema superou o conceito de transação -> procure compensação do fluxo de trabalho.

Boa sorte -

Eu preferiria retornar uma coleção de objetos de erro feitos personalizados, identificando o objeto, que é efetuado pelo erro, código de erro e descrição. Dessa forma, os erros podem ser tentados remediar ou exibidos ainda mais para o usuário.

Eu acho que você realmente está usando exceções se está pensando nesses termos!

É perfeitamente bom retornar valores que significam falha, em vez de jogar uma exceção. Muitas vezes é melhor. Exceções são melhor usadas quando você não pode se recuperar no nível de abstração em que está, mas você não deve usá -las como o principal meio de fluxo de controle ou seus programas ficarão muito difíceis de ler.

O serviço da web não retorna exceções, ele retorna códigos e mensagens de retorno. Eu armazenaria uma representação útil que apresenta as informações devolvidas e devolvesse a lista daquelas para a visualização ou o que quer que esteja olhando para ela.

Idealmente, a chamada para o seu serviço da Web deve ser assim.

List<Person> selectedPeople = ... //fetch list of people
callLotteryNumberWebService(selectedPeople.ToArray );

Fazer uma chamada de serviço da web para cada pessoa é caro. No final do servidor, você precisa iterar sobre a lista e executar a operação. O código lateral do servidor pode lançar 2 exceções: BulkoperfiledException - se houver um erro fatal devido a db para baixo ou arquivo de configuração ausente. Processamento adicional não é possível. BulkoperationException - Isso contém uma variedade de exceções referentes a uma pessoa. Você pode persistir algum ID para se referir exclusivamente a cada objeto. Seu código será assim:

List<Person> selectedPeople = ... // fetch list of people 

try{
    callLotteryNumberWebService(selectedPeople.ToArray);
}catch  (BulkOperationFailedException e) {
    SOP("Some config file missing/db down.No person records processed")
}catch(BulkOperationException e)  {
    UserDefinedExceptions us =  e.getExceptions()
    foreach(exception ein us)   {
        // read unique id to find which person object failed
    }
}

construct msg based on which personobject succeeded and which failed

A operação é considerada bem -sucedida quando nenhuma exceção é lançada. Você pode ter códigos de erro personalizados para falhas em vez de usar exceções definidas pelo usuário. Construir a BulkoperationException no lado do servidor é complicado. Segundo, você precisa categorizar erros jogados do lado do servidor para a BulkoperationFailedException e BulkoperException. Foi assim que eu lidei em um dos meus projetos

Eu olhava para Dtos Para esse tipo de tarefa. O DTO também pode incluir informações sobre se o persistente foi bem -sucedido ou não e outros tipos de "metadados".

Eu provavelmente retornaria um mapa de resultados do tipo Map<Person,Future<String>> do meu getLotteryNumbers<Collection<Person>> serviço.

Eu itera pelo mapa e usaria o Future.get() Para obter o número da loteria ou a exceção.

Em alguns dos meus serviços, gosto de implementar todas as chamadas como chamadas únicas e, em seguida, ter lógica em meu serviço para empatá -las e processá -las como um grupo. O lote é implementado usando um LinkedBlockingQueue e um tópico de votação.

Nesse cenário, volto um Future<Thing> que aguarda os resultados do lote estarem disponíveis usando um CountdownLatch.

Dê uma olhada na concorrência de Java na prática para ver como esses componentes podem funcionar juntos http://jcip.net/

Outra maneira, especialmente para sistemas de alto rendimento, seria usar um design baseado em filas em que uma entidade de processamento executaria operações em um objeto e, em seguida, com base nos resultados colocando o objeto em diferentes filas para processamento adicional por outras entidades e depois seguir em frente . Isso reduziria gargalos que surgiriam devido a processamento adicional que seria necessário, por exemplo, processamento de ordem para produtos fora de estoque

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top