J'utilise Spring Cloud pour implémenter mon système de micro services, une plateforme de vente de tickets. Le scénario est, il y a un proxy zuul, un registre eureka, et 3 service: service utilisateur, service de commande et service de billetterie. Les services utilisent un client REST déclaratif feignant pour communiquer entre eux.Comment implémenter une transaction distribuée avec hystrix fallback basée sur l'architecte Spring Cloud
Maintenant, il y a une fonction pour acheter des billets, le processus principal est comme suit: 1.
Service de commande accepte la demande de créer l'ordre
2. Service de commande créer entité ordres à l'état en attente.
3. de demander au service utilisateur de l'appel de service de traiter la paie de l'utilisateur.
4. de commander un service de ticket d'appel de service pour mettre à jour les tickets d'utilisateur.
5. service de mise à jour mettre à jour l'entité de commande comme FINI.
Et je veux utiliser Hystrix Fallback
pour mettre en œuvre la transaction. Par exemple, si le processus de paiement est terminé, mais qu'une erreur s'est produite lors du déplacement du ticket. Comment vérifier le paiement de l'utilisateur et le statut de la commande. Parce que le paiement de l'utilisateur est dans un autre service.
Ce qui suit est ma solution actuelle, je ne suis pas sûr que ce soit approprié. Ou y a-t-il une autre meilleure façon de le faire?
Au début, le OrderResource:
@RestController
@RequestMapping("/api/order")
public class OrderResource {
@HystrixCommand(fallbackMethod = "createFallback")
@PostMapping(value = "/")
public Order create(@RequestBody Order order) {
return orderService.create(order);
}
private Order createFallback(Order order) {
return orderService.createFallback(order);
}
}
Puis le OrderService:
@Service
public class OrderService {
@Transactional
public Order create(Order order) {
order.setStatus("PENDING");
order = orderRepository.save(order);
UserPayDTO payDTO = new UserPayDTO();
userCompositeService.payForOrder(payDTO);
order.setStatus("PAID");
order = orderRepository.save(order);
ticketCompositeService.moveTickets(ticketIds, currentUserId);
order.setStatus("FINISHED");
order = orderRepository.save(order);
return order;
}
@Transactional
public Order createFallback(Order order) {
// order is the object processed in create(), there is Transaction in create(), so saving order will be rollback,
// but the order instance still exist.
if (order.getId() == null) { // order not saved even.
return null;
}
UserPayDTO payDTO = new UserPayDTO();
try {
if (order.getStatus() == "FINISHED") { // order finished, must be paid and ticket moved
userCompositeService.payForOrderFallback(payDTO);
ticketCompositeService.moveTicketsFallback(getTicketIdList(order.getTicketIds()), currentUserId);
} else if (order.getStatus() == "PAID") { // is paid, but not sure whether has error during ticket movement.
userCompositeService.payForOrderFallback(payDTO);
ticketCompositeService.moveTicketsFallback(getTicketIdList(order.getTicketIds()), currentUserId);
} else if (order.getStatus() == "PENDING") { // maybe have error during payment.
userCompositeService.payForOrderFallback(payDTO);
}
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
order.setStatus("FAILED");
orderRepository.save(order); // order saving is rollbacked during create(), I save it here to trace the failed orders.
return order;
}
}
Quelques points clés sont:
- En utilisant
@HystrixCommand
dans la méthodeOrderResource.create(order)
, avecfallback
fonction. - En cas d'erreur de création, l'instance
order
utilisée dansOrderResource.create(order)
sera à nouveau utilisée dans la fonction de repli. Bien que la persistance de ceorder
sera roll-backed. Mais les données dans cette instance peuvent toujours être utilisées pour vérifier le fonctionnement. - Donc j'utilise un statut: 'PENDING', 'PAID', 'FINISHED' pour vérifier si un appel de service est fait.
ticketCompositeService
etuserCompositeService
est un client feignant. Pour feindre la méthode clientpayForOrder()
, il existe une autre méthodepayForOrderFallback()
pour le repli.- Je dois m'assurer que les méthodes de repli peuvent être appelées plusieurs fois.
- J'ajoute
try/catch
pourticketCompositeService
etuserCompositeService
appel, pour m'assurer que la commande sera sauvegardée quand même avec le statut 'FAILED'.
Il semble que cette solution peut fonctionner le plus souvent. Sauf que, dans la fonction de secours, s'il y a une erreur dans userCompositeService.payForOrderFallback(payDTO);
, l'appel de service composite suivant ne sera pas appelé.
Et, un autre problème est, je pense que c'est trop compliqué. Donc, pour ce scénario, comment dois-je implémenter la transaction dist correctement et efficacement. Toute suggestion ou conseil vous aidera. Merci.
Vous devriez essayer Event Sourcing + technique CQRS [link] (https://stackoverflow.com/questions/44114755/how-to-do-2-phase-commit-between-two-micro-servicesspring-boot) – sathees