change according to cgeckstyle
This commit is contained in:
		| @@ -1,7 +1,7 @@ | ||||
| --- | ||||
| layout: pattern | ||||
| title: Saga | ||||
| folder: Communication | ||||
| folder: saga | ||||
| permalink: /patterns/saga/ | ||||
| categories: Behavioral | ||||
| tags: | ||||
| @@ -43,4 +43,4 @@ Use the Saga pattern, if: | ||||
| - you can not use 2PC(two phase commit) | ||||
|  | ||||
| ## Credits | ||||
| - [pattern description](https://microservices.io/patterns/data/saga.html) | ||||
| - [Pattern: Saga](https://microservices.io/patterns/data/saga.html) | ||||
							
								
								
									
										33
									
								
								saga/pom.xml
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								saga/pom.xml
									
									
									
									
									
								
							| @@ -23,22 +23,23 @@ | ||||
|     THE SOFTWARE. | ||||
|  | ||||
| --> | ||||
| <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" | ||||
|     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | ||||
|   <modelVersion>4.0.0</modelVersion> | ||||
|   <parent> | ||||
|     <groupId>com.iluwatar</groupId> | ||||
|     <artifactId>java-design-patterns</artifactId> | ||||
|     <version>1.22.0-SNAPSHOT</version> | ||||
|   </parent> | ||||
| <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | ||||
|          xmlns="http://maven.apache.org/POM/4.0.0" | ||||
|          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
|     <parent> | ||||
|         <groupId>com.iluwatar</groupId> | ||||
|         <artifactId>java-design-patterns</artifactId> | ||||
|         <version>1.22.0-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|   <artifactId>saga</artifactId> | ||||
|   <dependencies> | ||||
|     <dependency> | ||||
|       <groupId>junit</groupId> | ||||
|       <artifactId>junit</artifactId> | ||||
|       <scope>test</scope> | ||||
|     </dependency> | ||||
|   </dependencies> | ||||
|     <artifactId>saga</artifactId> | ||||
|     <dependencies> | ||||
|         <dependency> | ||||
|             <groupId>junit</groupId> | ||||
|             <artifactId>junit</artifactId> | ||||
|             <scope>test</scope> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
|  | ||||
| </project> | ||||
|   | ||||
| @@ -24,37 +24,40 @@ package com.iluwatar.saga.choreography; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Chapter is an interface representing a contract for an external service. | ||||
|  * ChoreographyChapter is an interface representing a contract for an external service. | ||||
|  * In that case, a service needs to make a decision what to do further | ||||
|  * hence the server needs to get all context representing {@link Saga} | ||||
|  * */ | ||||
| public interface Chapter { | ||||
|  */ | ||||
| public interface ChoreographyChapter { | ||||
| 
 | ||||
|     /** | ||||
|      * In that case, every method is responsible to make a decision on what to do then | ||||
|      * @param saga incoming saga | ||||
|      * @return saga result | ||||
|      */ | ||||
|     Saga execute(Saga saga); | ||||
|   /** | ||||
|    * In that case, every method is responsible to make a decision on what to do then | ||||
|    * | ||||
|    * @param saga incoming saga | ||||
|    * @return saga result | ||||
|    */ | ||||
|   Saga execute(Saga saga); | ||||
| 
 | ||||
|     /** | ||||
|      * @return service name. | ||||
|      */ | ||||
|     String getName(); | ||||
|   /** | ||||
|    * @return service name. | ||||
|    */ | ||||
|   String getName(); | ||||
| 
 | ||||
|     /** | ||||
|      * The operation executed in general case. | ||||
|      * @param saga incoming saga | ||||
|      * @return result {@link Saga} | ||||
|      */ | ||||
|     Saga process(Saga saga); | ||||
|   /** | ||||
|    * The operation executed in general case. | ||||
|    * | ||||
|    * @param saga incoming saga | ||||
|    * @return result {@link Saga} | ||||
|    */ | ||||
|   Saga process(Saga saga); | ||||
| 
 | ||||
|     /** | ||||
|      * The operation executed in rollback case. | ||||
|      * @param saga incoming saga | ||||
|      * @return result {@link Saga} | ||||
|      */ | ||||
|     Saga rollback(Saga saga); | ||||
|   /** | ||||
|    * The operation executed in rollback case. | ||||
|    * | ||||
|    * @param saga incoming saga | ||||
|    * @return result {@link Saga} | ||||
|    */ | ||||
|   Saga rollback(Saga saga); | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @@ -27,12 +27,12 @@ package com.iluwatar.saga.choreography; | ||||
|  * Class representing a service to book a fly | ||||
|  */ | ||||
| public class FlyBookingService extends Service { | ||||
|     public FlyBookingService(ServiceDiscoveryService service) { | ||||
|         super(service); | ||||
|     } | ||||
|   public FlyBookingService(ServiceDiscoveryService service) { | ||||
|     super(service); | ||||
|   } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return "booking a Fly"; | ||||
|     } | ||||
|   @Override | ||||
|   public String getName() { | ||||
|     return "booking a Fly"; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -27,14 +27,14 @@ package com.iluwatar.saga.choreography; | ||||
|  * Class representing a service to book a hotel | ||||
|  */ | ||||
| public class HotelBookingService extends Service { | ||||
|     public HotelBookingService(ServiceDiscoveryService service) { | ||||
|         super(service); | ||||
|     } | ||||
|   public HotelBookingService(ServiceDiscoveryService service) { | ||||
|     super(service); | ||||
|   } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return "booking a Hotel"; | ||||
|     } | ||||
|   @Override | ||||
|   public String getName() { | ||||
|     return "booking a Hotel"; | ||||
|   } | ||||
|  | ||||
|  | ||||
|    } | ||||
| } | ||||
|   | ||||
| @@ -26,14 +26,14 @@ package com.iluwatar.saga.choreography; | ||||
| /** | ||||
|  * Class representing a service to init a new order. | ||||
|  */ | ||||
| public class OrderService extends Service{ | ||||
| public class OrderService extends Service { | ||||
|  | ||||
|     public OrderService(ServiceDiscoveryService service) { | ||||
|         super(service); | ||||
|     } | ||||
|   public OrderService(ServiceDiscoveryService service) { | ||||
|     super(service); | ||||
|   } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return "init an order"; | ||||
|     } | ||||
|   @Override | ||||
|   public String getName() { | ||||
|     return "init an order"; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -29,131 +29,187 @@ import java.util.List; | ||||
| /** | ||||
|  * Saga representation. | ||||
|  * Saga consists of chapters. | ||||
|  * Every Chapter is executed a certain service. | ||||
|  * Every ChoreographyChapter is executed a certain service. | ||||
|  */ | ||||
| public class Saga { | ||||
|  | ||||
|     private List<Chapter> chapters; | ||||
|     private int pos; | ||||
|     private boolean forward; | ||||
|     private boolean finished; | ||||
|   private List<Chapter> chapters; | ||||
|   private int pos; | ||||
|   private boolean forward; | ||||
|   private boolean finished; | ||||
|  | ||||
|  | ||||
|     public static Saga create() { | ||||
|         return new Saga(); | ||||
|     } | ||||
|     public SagaResult getResult() { | ||||
|         return !finished ? | ||||
|                 SagaResult.PROGRESS : | ||||
|                 forward ? | ||||
|                         SagaResult.FINISHED : | ||||
|                         SagaResult.ROLLBACKED; | ||||
|     } | ||||
|     public Saga chapter(String name) { | ||||
|         this.chapters.add(new Chapter(name)); | ||||
|         return this; | ||||
|     } | ||||
|     public Saga setInValue(Object value){ | ||||
|         if(chapters.isEmpty()){ | ||||
|             return this; | ||||
|         } | ||||
|         chapters.get(chapters.size()-1).setInValue(value); | ||||
|         return this; | ||||
|     } | ||||
|     public Object getCurrentValue(){ | ||||
|         return chapters.get(pos).getInValue(); | ||||
|     } | ||||
|     public void setCurrentValue(Object value){ | ||||
|         chapters.get(pos).setInValue(value); | ||||
|     } | ||||
|     public void setCurrentStatus(ChapterResult result){ | ||||
|         chapters.get(pos).setResult(result); | ||||
|   public static Saga create() { | ||||
|     return new Saga(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * get resuzlt of saga | ||||
|    * | ||||
|    * @return result of saga @see {@link SagaResult} | ||||
|    */ | ||||
|   public SagaResult getResult() { | ||||
|     if (finished) { | ||||
|       return forward | ||||
|           ? SagaResult.FINISHED | ||||
|           : SagaResult.ROLLBACKED; | ||||
|     } | ||||
|  | ||||
|     void setFinished(boolean finished) { | ||||
|         this.finished = finished; | ||||
|     } | ||||
|     boolean isForward() { | ||||
|         return forward; | ||||
|     } | ||||
|     int forward() { | ||||
|         return ++pos; | ||||
|     } | ||||
|     return SagaResult.PROGRESS; | ||||
|   } | ||||
|  | ||||
|     int back() { | ||||
|         this.forward = false; | ||||
|         return --pos; | ||||
|   /** | ||||
|    * add chapter to saga | ||||
|    * @param name chapter name | ||||
|    * @return this | ||||
|    */ | ||||
|   public Saga chapter(String name) { | ||||
|     this.chapters.add(new Chapter(name)); | ||||
|     return this; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * set value to last chapter | ||||
|    * @param value invalue | ||||
|    * @return this | ||||
|    */ | ||||
|   public Saga setInValue(Object value) { | ||||
|     if (chapters.isEmpty()) { | ||||
|       return this; | ||||
|     } | ||||
|     chapters.get(chapters.size() - 1).setInValue(value); | ||||
|     return this; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * get value from current chapter | ||||
|    * @return value | ||||
|    */ | ||||
|   public Object getCurrentValue() { | ||||
|     return chapters.get(pos).getInValue(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * set value to current chapter | ||||
|    * @param value to set | ||||
|    */ | ||||
|   public void setCurrentValue(Object value) { | ||||
|     chapters.get(pos).setInValue(value); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * set status for current chapter | ||||
|    * @param result to set | ||||
|    */ | ||||
|   public void setCurrentStatus(ChapterResult result) { | ||||
|     chapters.get(pos).setResult(result); | ||||
|   } | ||||
|  | ||||
|   void setFinished(boolean finished) { | ||||
|     this.finished = finished; | ||||
|   } | ||||
|  | ||||
|   boolean isForward() { | ||||
|     return forward; | ||||
|   } | ||||
|  | ||||
|   int forward() { | ||||
|     return ++pos; | ||||
|   } | ||||
|  | ||||
|   int back() { | ||||
|     this.forward = false; | ||||
|     return --pos; | ||||
|   } | ||||
|  | ||||
|  | ||||
|     public Saga() { | ||||
|         this.chapters = new ArrayList<>(); | ||||
|         this.pos = 0; | ||||
|         this.forward = true; | ||||
|         this.finished = false; | ||||
|     } | ||||
|   private Saga() { | ||||
|     this.chapters = new ArrayList<>(); | ||||
|     this.pos = 0; | ||||
|     this.forward = true; | ||||
|     this.finished = false; | ||||
|   } | ||||
|  | ||||
|     Chapter getCurrent() { | ||||
|         return chapters.get(pos); | ||||
|     } | ||||
|   Chapter getCurrent() { | ||||
|     return chapters.get(pos); | ||||
|   } | ||||
|  | ||||
|  | ||||
|     boolean isPresent() { | ||||
|         return pos >= 0 && pos < chapters.size(); | ||||
|     } | ||||
|     boolean isCurrentSuccess(){ | ||||
|         return chapters.get(pos).isSuccess(); | ||||
|   boolean isPresent() { | ||||
|     return pos >= 0 && pos < chapters.size(); | ||||
|   } | ||||
|  | ||||
|   boolean isCurrentSuccess() { | ||||
|     return chapters.get(pos).isSuccess(); | ||||
|   } | ||||
|  | ||||
|   /*** | ||||
|    * Class presents a chapter status and incoming parameters(incoming parameter transforms to outcoming parameter) | ||||
|    */ | ||||
|   public static class Chapter { | ||||
|     private String name; | ||||
|     private ChapterResult result; | ||||
|     private Object inValue; | ||||
|  | ||||
|  | ||||
|     public Chapter(String name) { | ||||
|       this.name = name; | ||||
|       this.result = ChapterResult.INIT; | ||||
|     } | ||||
|  | ||||
|     /*** | ||||
|      * Class presents a chapter status and incoming parameters(incoming parameter transforms to outcoming parameter) | ||||
|     public Object getInValue() { | ||||
|       return inValue; | ||||
|     } | ||||
|  | ||||
|     public void setInValue(Object object) { | ||||
|       this.inValue = object; | ||||
|     } | ||||
|  | ||||
|     public String getName() { | ||||
|       return name; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * set result | ||||
|      * @param result {@link ChapterResult} | ||||
|      */ | ||||
|     public static class Chapter { | ||||
|         private String name; | ||||
|         private ChapterResult result; | ||||
|         private Object inValue; | ||||
|  | ||||
|  | ||||
|         public Chapter(String name) { | ||||
|             this.name = name; | ||||
|             this.result = ChapterResult.INIT; | ||||
|         } | ||||
|  | ||||
|         public Object getInValue() { | ||||
|             return inValue; | ||||
|         } | ||||
|  | ||||
|         public void setInValue(Object object) { | ||||
|             this.inValue = object; | ||||
|         } | ||||
|  | ||||
|         public String getName() { | ||||
|             return name; | ||||
|         } | ||||
|         public void setResult(ChapterResult result){ | ||||
|             this.result = result; | ||||
|         } | ||||
|  | ||||
|         public boolean isSuccess(){ | ||||
|             return result == ChapterResult.SUCCESS; | ||||
|         } | ||||
|     public void setResult(ChapterResult result) { | ||||
|       this.result = result; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public enum ChapterResult { | ||||
|         INIT, SUCCESS, ROLLBACK | ||||
|     /** | ||||
|      * the result for chapter is good | ||||
|      * @return true if is good otherwise bad | ||||
|      */ | ||||
|     public boolean isSuccess() { | ||||
|       return result == ChapterResult.SUCCESS; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     public enum SagaResult { | ||||
|         PROGRESS, FINISHED, ROLLBACKED | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return "Saga{" + | ||||
|                 "chapters=" + Arrays.toString(chapters.toArray()) + | ||||
|                 ", pos=" + pos + | ||||
|                 ", forward=" + forward + | ||||
|                 '}'; | ||||
|     } | ||||
|   /** | ||||
|    * result for chapter | ||||
|    */ | ||||
|   public enum ChapterResult { | ||||
|     INIT, SUCCESS, ROLLBACK | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * result for saga | ||||
|    */ | ||||
|   public enum SagaResult { | ||||
|     PROGRESS, FINISHED, ROLLBACKED | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public String toString() { | ||||
|     return "Saga{" | ||||
|         + "chapters=" | ||||
|         + Arrays.toString(chapters.toArray()) | ||||
|         + ", pos=" | ||||
|         + pos | ||||
|         + ", forward=" | ||||
|         + forward | ||||
|         + '}'; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -44,34 +44,37 @@ import org.slf4j.LoggerFactory; | ||||
|  * @see Service | ||||
|  */ | ||||
| public class SagaApplication { | ||||
|     private static final Logger logger = LoggerFactory.getLogger(SagaApplication.class); | ||||
|   private static final Logger LOGGER = LoggerFactory.getLogger(SagaApplication.class); | ||||
|  | ||||
|     public static void main(String[] args) { | ||||
|         ServiceDiscoveryService sd = serviceDiscovery(); | ||||
|         Chapter service = sd.findAny(); | ||||
|         Saga goodOrderSaga = service.execute(newSaga("good_order")); | ||||
|         Saga badOrderSaga = service.execute(newSaga("bad_order")); | ||||
|         logger.info("orders: goodOrder is {}, badOrder is {}", | ||||
|                 goodOrderSaga.getResult(), badOrderSaga.getResult()); | ||||
|   /** | ||||
|    * main method | ||||
|    */ | ||||
|   public static void main(String[] args) { | ||||
|     ServiceDiscoveryService sd = serviceDiscovery(); | ||||
|     ChoreographyChapter service = sd.findAny(); | ||||
|     Saga goodOrderSaga = service.execute(newSaga("good_order")); | ||||
|     Saga badOrderSaga = service.execute(newSaga("bad_order")); | ||||
|     LOGGER.info("orders: goodOrder is {}, badOrder is {}", | ||||
|         goodOrderSaga.getResult(), badOrderSaga.getResult()); | ||||
|  | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  | ||||
|     private static Saga newSaga(Object value) { | ||||
|         return Saga | ||||
|                 .create() | ||||
|                 .chapter("init an order").setInValue(value) | ||||
|                 .chapter("booking a Fly") | ||||
|                 .chapter("booking a Hotel") | ||||
|                 .chapter("withdrawing Money"); | ||||
|     } | ||||
|   private static Saga newSaga(Object value) { | ||||
|     return Saga | ||||
|         .create() | ||||
|         .chapter("init an order").setInValue(value) | ||||
|         .chapter("booking a Fly") | ||||
|         .chapter("booking a Hotel") | ||||
|         .chapter("withdrawing Money"); | ||||
|   } | ||||
|  | ||||
|     private static ServiceDiscoveryService serviceDiscovery() { | ||||
|         ServiceDiscoveryService sd = new ServiceDiscoveryService(); | ||||
|         return sd | ||||
|                 .discover(new OrderService(sd)) | ||||
|                 .discover(new FlyBookingService(sd)) | ||||
|                 .discover(new HotelBookingService(sd)) | ||||
|                 .discover(new WithdrawMoneyService(sd)); | ||||
|     } | ||||
|   private static ServiceDiscoveryService serviceDiscovery() { | ||||
|     ServiceDiscoveryService sd = new ServiceDiscoveryService(); | ||||
|     return sd | ||||
|         .discover(new OrderService(sd)) | ||||
|         .discover(new FlyBookingService(sd)) | ||||
|         .discover(new HotelBookingService(sd)) | ||||
|         .discover(new WithdrawMoneyService(sd)); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -25,79 +25,85 @@ package com.iluwatar.saga.choreography; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import java.util.function.Supplier; | ||||
|  | ||||
| /** | ||||
|  * Common abstraction class representing services | ||||
|  * implementing a general contract @see {@link Chapter} | ||||
|  * implementing a general contract @see {@link ChoreographyChapter} | ||||
|  */ | ||||
| public abstract class Service implements Chapter { | ||||
|     protected static final Logger logger = LoggerFactory.getLogger(Service.class); | ||||
| public abstract class Service implements ChoreographyChapter { | ||||
|   protected static final Logger LOGGER = LoggerFactory.getLogger(Service.class); | ||||
|  | ||||
|     private final ServiceDiscoveryService sd; | ||||
|   private final ServiceDiscoveryService sd; | ||||
|  | ||||
|     public Service(ServiceDiscoveryService service) { | ||||
|         this.sd = service; | ||||
|     } | ||||
|   public Service(ServiceDiscoveryService service) { | ||||
|     this.sd = service; | ||||
|   } | ||||
|  | ||||
|     @Override | ||||
|     public Saga execute(Saga saga) { | ||||
|         Saga nextSaga = saga; | ||||
|         Object nextVal; | ||||
|         String chapterName = saga.getCurrent().getName(); | ||||
|         if (chapterName.equals(getName())) { | ||||
|             if (saga.isForward()) { | ||||
|                 nextSaga = process(saga); | ||||
|                 nextVal = nextSaga.getCurrentValue(); | ||||
|                 if (nextSaga.isCurrentSuccess()) { | ||||
|                     nextSaga.forward(); | ||||
|                 } else { | ||||
|                     nextSaga.back(); | ||||
|                 } | ||||
|             } else { | ||||
|                 nextSaga = rollback(saga); | ||||
|                 nextVal = nextSaga.getCurrentValue(); | ||||
|                 nextSaga.back(); | ||||
|             } | ||||
|  | ||||
|             if (isSagaFinished(nextSaga)) { | ||||
|                 return nextSaga; | ||||
|             } | ||||
|  | ||||
|             nextSaga.setCurrentValue(nextVal); | ||||
|   @Override | ||||
|   public Saga execute(Saga saga) { | ||||
|     Saga nextSaga = saga; | ||||
|     Object nextVal; | ||||
|     String chapterName = saga.getCurrent().getName(); | ||||
|     if (chapterName.equals(getName())) { | ||||
|       if (saga.isForward()) { | ||||
|         nextSaga = process(saga); | ||||
|         nextVal = nextSaga.getCurrentValue(); | ||||
|         if (nextSaga.isCurrentSuccess()) { | ||||
|           nextSaga.forward(); | ||||
|         } else { | ||||
|           nextSaga.back(); | ||||
|         } | ||||
|         Saga finalNextSaga = nextSaga; | ||||
|       } else { | ||||
|         nextSaga = rollback(saga); | ||||
|         nextVal = nextSaga.getCurrentValue(); | ||||
|         nextSaga.back(); | ||||
|       } | ||||
|  | ||||
|         return sd.find(chapterName).map(ch -> ch.execute(finalNextSaga)) | ||||
|                 .orElseThrow(RuntimeException::new); | ||||
|       if (isSagaFinished(nextSaga)) { | ||||
|         return nextSaga; | ||||
|       } | ||||
|  | ||||
|       nextSaga.setCurrentValue(nextVal); | ||||
|     } | ||||
|     Saga finalNextSaga = nextSaga; | ||||
|  | ||||
|     @Override | ||||
|     public Saga process(Saga saga) { | ||||
|         Object inValue = saga.getCurrentValue(); | ||||
|         logger.info("The chapter '{}' has been started. The data {} has been stored or calculated successfully", | ||||
|                 getName(), inValue); | ||||
|         saga.setCurrentStatus(Saga.ChapterResult.SUCCESS); | ||||
|         saga.setCurrentValue(inValue); | ||||
|         return saga; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Saga rollback(Saga saga) { | ||||
|         Object inValue = saga.getCurrentValue(); | ||||
|         logger.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully", | ||||
|                 getName(), inValue); | ||||
|  | ||||
|         saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK); | ||||
|         saga.setCurrentValue(inValue); | ||||
|         return saga; | ||||
|     } | ||||
|  | ||||
|     private boolean isSagaFinished(Saga saga) { | ||||
|         if (!saga.isPresent()) { | ||||
|             saga.setFinished(true); | ||||
|             logger.info(" the saga has been finished with {} status", saga.getResult()); | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     return sd.find(chapterName).map(ch -> ch.execute(finalNextSaga)) | ||||
|         .orElseThrow(serviceNotFoundException(chapterName)); | ||||
|   } | ||||
|  | ||||
|   private Supplier<RuntimeException> serviceNotFoundException(String chServiceName) { | ||||
|     return () -> new RuntimeException(String.format("the service %s has not been found", chServiceName)); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public Saga process(Saga saga) { | ||||
|     Object inValue = saga.getCurrentValue(); | ||||
|     LOGGER.info("The chapter '{}' has been started. The data {} has been stored or calculated successfully", | ||||
|         getName(), inValue); | ||||
|     saga.setCurrentStatus(Saga.ChapterResult.SUCCESS); | ||||
|     saga.setCurrentValue(inValue); | ||||
|     return saga; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public Saga rollback(Saga saga) { | ||||
|     Object inValue = saga.getCurrentValue(); | ||||
|     LOGGER.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully", | ||||
|         getName(), inValue); | ||||
|  | ||||
|     saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK); | ||||
|     saga.setCurrentValue(inValue); | ||||
|     return saga; | ||||
|   } | ||||
|  | ||||
|   private boolean isSagaFinished(Saga saga) { | ||||
|     if (!saga.isPresent()) { | ||||
|       saga.setFinished(true); | ||||
|       LOGGER.info(" the saga has been finished with {} status", saga.getResult()); | ||||
|       return true; | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -32,29 +32,30 @@ import java.util.Optional; | ||||
|  * The class representing a service discovery pattern. | ||||
|  */ | ||||
| public class ServiceDiscoveryService { | ||||
|     private Map<String, Chapter> services; | ||||
|   private Map<String, ChoreographyChapter> services; | ||||
|  | ||||
|     /** | ||||
|      * find any service | ||||
|      * @return any service | ||||
|      * @throws NoSuchElementException if no elements further | ||||
|      */ | ||||
|     public Chapter findAny(){ | ||||
|        return services.values().iterator().next(); | ||||
|     } | ||||
|   /** | ||||
|    * find any service | ||||
|    * | ||||
|    * @return any service | ||||
|    * @throws NoSuchElementException if no elements further | ||||
|    */ | ||||
|   public ChoreographyChapter findAny() { | ||||
|     return services.values().iterator().next(); | ||||
|   } | ||||
|  | ||||
|     public Optional<Chapter> find(String service) { | ||||
|         return Optional.ofNullable(services.getOrDefault(service, null)); | ||||
|     } | ||||
|   public Optional<ChoreographyChapter> find(String service) { | ||||
|     return Optional.ofNullable(services.getOrDefault(service, null)); | ||||
|   } | ||||
|  | ||||
|     public ServiceDiscoveryService discover(Chapter chapterService) { | ||||
|         services.put(chapterService.getName(), chapterService); | ||||
|         return this; | ||||
|     } | ||||
|   public ServiceDiscoveryService discover(ChoreographyChapter chapterService) { | ||||
|     services.put(chapterService.getName(), chapterService); | ||||
|     return this; | ||||
|   } | ||||
|  | ||||
|     public ServiceDiscoveryService() { | ||||
|         this.services = new HashMap<>(); | ||||
|     } | ||||
|   public ServiceDiscoveryService() { | ||||
|     this.services = new HashMap<>(); | ||||
|   } | ||||
|  | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -28,26 +28,26 @@ package com.iluwatar.saga.choreography; | ||||
|  */ | ||||
| public class WithdrawMoneyService extends Service { | ||||
|  | ||||
|     public WithdrawMoneyService(ServiceDiscoveryService service) { | ||||
|         super(service); | ||||
|     } | ||||
|   public WithdrawMoneyService(ServiceDiscoveryService service) { | ||||
|     super(service); | ||||
|   } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return "withdrawing Money"; | ||||
|     } | ||||
|   @Override | ||||
|   public String getName() { | ||||
|     return "withdrawing Money"; | ||||
|   } | ||||
|  | ||||
|     @Override | ||||
|     public Saga process(Saga saga) { | ||||
|         Object inValue = saga.getCurrentValue(); | ||||
|   @Override | ||||
|   public Saga process(Saga saga) { | ||||
|     Object inValue = saga.getCurrentValue(); | ||||
|  | ||||
|         if (inValue.equals("bad_order") ) { | ||||
|             logger.info("The chapter '{}' has been started. But the exception has been raised." + | ||||
|                             "The rollback is about to start", | ||||
|                     getName(), inValue); | ||||
|             saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK); | ||||
|             return saga; | ||||
|         } | ||||
|         return super.process(saga); | ||||
|     if (inValue.equals("bad_order")) { | ||||
|       LOGGER.info("The chapter '{}' has been started. But the exception has been raised." | ||||
|               + "The rollback is about to start", | ||||
|           getName(), inValue); | ||||
|       saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK); | ||||
|       return saga; | ||||
|     } | ||||
|     return super.process(saga); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -24,34 +24,38 @@ package com.iluwatar.saga.orchestration; | ||||
|  | ||||
| /** | ||||
|  * Executing result for chapter | ||||
|  * | ||||
|  * @param <K> incoming value | ||||
|  */ | ||||
| public class ChapterResult<K> { | ||||
|     private K value; | ||||
|     private State state; | ||||
|   private K value; | ||||
|   private State state; | ||||
|  | ||||
|     public K getValue() { | ||||
|         return value; | ||||
|     } | ||||
|   public K getValue() { | ||||
|     return value; | ||||
|   } | ||||
|  | ||||
|     ChapterResult(K value, State state) { | ||||
|         this.value = value; | ||||
|         this.state = state; | ||||
|     } | ||||
|   ChapterResult(K value, State state) { | ||||
|     this.value = value; | ||||
|     this.state = state; | ||||
|   } | ||||
|  | ||||
|     public boolean isSuccess(){ | ||||
|         return state == State.SUCCESS; | ||||
|     } | ||||
|   public boolean isSuccess() { | ||||
|     return state == State.SUCCESS; | ||||
|   } | ||||
|  | ||||
|     public static <K> ChapterResult<K> success(K val) { | ||||
|         return new ChapterResult<>(val, State.SUCCESS); | ||||
|     } | ||||
|   public static <K> ChapterResult<K> success(K val) { | ||||
|     return new ChapterResult<>(val, State.SUCCESS); | ||||
|   } | ||||
|  | ||||
|     public static <K> ChapterResult<K> failure(K val) { | ||||
|         return new ChapterResult<>(val, State.FAILURE); | ||||
|     } | ||||
|   public static <K> ChapterResult<K> failure(K val) { | ||||
|     return new ChapterResult<>(val, State.FAILURE); | ||||
|   } | ||||
|  | ||||
|     public enum State { | ||||
|         SUCCESS, FAILURE | ||||
|     } | ||||
|   /** | ||||
|    * state for chapter | ||||
|    */ | ||||
|   public enum State { | ||||
|     SUCCESS, FAILURE | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -26,8 +26,8 @@ package com.iluwatar.saga.orchestration; | ||||
|  * Class representing a service to book a fly | ||||
|  */ | ||||
| public class FlyBookingService extends Service<String> { | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return "booking a Fly"; | ||||
|     } | ||||
|   @Override | ||||
|   public String getName() { | ||||
|     return "booking a Fly"; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -26,25 +26,25 @@ package com.iluwatar.saga.orchestration; | ||||
|  * Class representing a service to book a hotel | ||||
|  */ | ||||
| public class HotelBookingService extends Service<String> { | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return "booking a Hotel"; | ||||
|   @Override | ||||
|   public String getName() { | ||||
|     return "booking a Hotel"; | ||||
|   } | ||||
|  | ||||
|  | ||||
|   @Override | ||||
|   public ChapterResult<String> rollback(String value) { | ||||
|     if (value.equals("crashed_order")) { | ||||
|       LOGGER.info("The Rollback for a chapter '{}' has been started. " | ||||
|               + "The data {} has been failed.The saga has been crashed.", | ||||
|           getName(), value); | ||||
|  | ||||
|       return ChapterResult.failure(value); | ||||
|     } | ||||
|  | ||||
|     LOGGER.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully", | ||||
|         getName(), value); | ||||
|  | ||||
|     @Override | ||||
|     public ChapterResult<String> rollback(String value) { | ||||
|         if(value.equals("crashed_order")){ | ||||
|             logger.info("The Rollback for a chapter '{}' has been started. " + | ||||
|                             "The data {} has been failed.The saga has been crashed.", | ||||
|                     getName(), value); | ||||
|  | ||||
|             return ChapterResult.failure(value); | ||||
|         } | ||||
|  | ||||
|         logger.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully", | ||||
|                 getName(), value); | ||||
|  | ||||
|         return super.rollback(value); | ||||
|     } | ||||
|     return super.rollback(value); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -23,28 +23,31 @@ | ||||
| package com.iluwatar.saga.orchestration; | ||||
| 
 | ||||
| /** | ||||
|  * Chapter is an interface representing a contract for an external service. | ||||
|  * ChoreographyChapter is an interface representing a contract for an external service. | ||||
|  * | ||||
|  * @param <K> is type for passing params | ||||
|  */ | ||||
| public interface Chapter<K> { | ||||
| public interface OrchestrationChapter<K> { | ||||
| 
 | ||||
|     /** | ||||
|      * @return service name. | ||||
|      */ | ||||
|     String getName(); | ||||
|   /** | ||||
|    * @return service name. | ||||
|    */ | ||||
|   String getName(); | ||||
| 
 | ||||
|     /** | ||||
|      * The operation executed in general case. | ||||
|      * @param value incoming value | ||||
|      * @return result {@link ChapterResult} | ||||
|      */ | ||||
|     ChapterResult<K> process(K value); | ||||
|   /** | ||||
|    * The operation executed in general case. | ||||
|    * | ||||
|    * @param value incoming value | ||||
|    * @return result {@link ChapterResult} | ||||
|    */ | ||||
|   ChapterResult<K> process(K value); | ||||
| 
 | ||||
|     /** | ||||
|      * The operation executed in rollback case. | ||||
|      * @param value incoming value | ||||
|      * @return result {@link ChapterResult} | ||||
|      */ | ||||
|     ChapterResult<K> rollback(K value); | ||||
|   /** | ||||
|    * The operation executed in rollback case. | ||||
|    * | ||||
|    * @param value incoming value | ||||
|    * @return result {@link ChapterResult} | ||||
|    */ | ||||
|   ChapterResult<K> rollback(K value); | ||||
| 
 | ||||
| } | ||||
| @@ -26,8 +26,8 @@ package com.iluwatar.saga.orchestration; | ||||
|  * Class representing a service to init a new order. | ||||
|  */ | ||||
| public class OrderService extends Service<String> { | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return "init an order"; | ||||
|     } | ||||
|   @Override | ||||
|   public String getName() { | ||||
|     return "init an order"; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -28,49 +28,56 @@ import java.util.List; | ||||
| /** | ||||
|  * Saga representation. | ||||
|  * Saga consists of chapters. | ||||
|  * Every Chapter is executed by a certain service. | ||||
|  * Every ChoreographyChapter is executed by a certain service. | ||||
|  */ | ||||
| public class Saga { | ||||
|  | ||||
|     private List<Chapter> chapters; | ||||
|   private List<Chapter> chapters; | ||||
|  | ||||
|     public Saga() { | ||||
|         this.chapters = new ArrayList<>(); | ||||
|  | ||||
|   private Saga() { | ||||
|     this.chapters = new ArrayList<>(); | ||||
|   } | ||||
|  | ||||
|  | ||||
|   public Saga chapter(String name) { | ||||
|     this.chapters.add(new Chapter(name)); | ||||
|     return this; | ||||
|   } | ||||
|  | ||||
|  | ||||
|   public Chapter get(int idx) { | ||||
|     return chapters.get(idx); | ||||
|   } | ||||
|  | ||||
|   public boolean isPresent(int idx) { | ||||
|     return idx >= 0 && idx < chapters.size(); | ||||
|   } | ||||
|  | ||||
|  | ||||
|   public static Saga create() { | ||||
|     return new Saga(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * result for saga | ||||
|    */ | ||||
|   public enum Result { | ||||
|     FINISHED, ROLLBACK, CRASHED | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * class represents chapter name | ||||
|    */ | ||||
|   public static class Chapter { | ||||
|     String name; | ||||
|  | ||||
|     public Chapter(String name) { | ||||
|       this.name = name; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public Saga chapter(String name) { | ||||
|         this.chapters.add(new Chapter(name)); | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public Chapter get(int idx) { | ||||
|         return chapters.get(idx); | ||||
|     } | ||||
|  | ||||
|     public boolean isPresent(int idx) { | ||||
|         return idx >= 0 && idx < chapters.size(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public static Saga create() { | ||||
|         return new Saga(); | ||||
|     } | ||||
|  | ||||
|     public enum Result { | ||||
|         FINISHED, ROLLBACK, CRASHED | ||||
|     } | ||||
|  | ||||
|     public static class Chapter { | ||||
|         String name; | ||||
|  | ||||
|         public Chapter(String name) { | ||||
|             this.name = name; | ||||
|         } | ||||
|  | ||||
|         public String getName() { | ||||
|             return name; | ||||
|         } | ||||
|     public String getName() { | ||||
|       return name; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -31,11 +31,12 @@ import org.slf4j.LoggerFactory; | ||||
|  * This pattern is used in distributed services to perform a group of operations atomically. | ||||
|  * This is an analog of transaction in a database but in terms of microservices architecture this is executed | ||||
|  * in a distributed environment | ||||
|  * | ||||
|  * <p> | ||||
|  * A saga is a sequence of local transactions in a certain context. If one transaction fails for some reason, | ||||
|  * the saga executes compensating transactions(rollbacks) to undo the impact of the preceding transactions. | ||||
|  * | ||||
|  * In this approach, there is an orchestrator @see {@link SagaOrchestrator} that manages all the transactions and directs | ||||
|  * <p> | ||||
|  * In this approach, there is an orchestrator @see {@link SagaOrchestrator} | ||||
|  * that manages all the transactions and directs | ||||
|  * the participant services to execute local transactions based on events. | ||||
|  * The major difference with choreography saga is an ability to handle crashed services | ||||
|  * (otherwise in choreography services very hard to prevent a saga if one of them has been crashed) | ||||
| @@ -45,34 +46,37 @@ import org.slf4j.LoggerFactory; | ||||
|  * @see Service | ||||
|  */ | ||||
| public class SagaApplication { | ||||
|     private static final Logger logger = LoggerFactory.getLogger(SagaApplication.class); | ||||
|   private static final Logger LOGGER = LoggerFactory.getLogger(SagaApplication.class); | ||||
|  | ||||
|     public static void main(String[] args) { | ||||
|         SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery()); | ||||
|   /** | ||||
|    * method to show common saga logic | ||||
|    */ | ||||
|   public static void main(String[] args) { | ||||
|     SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery()); | ||||
|  | ||||
|         Saga.Result goodOrder = sagaOrchestrator.execute("good_order"); | ||||
|         Saga.Result badOrder = sagaOrchestrator.execute("bad_order"); | ||||
|         Saga.Result crashedOrder = sagaOrchestrator.execute("crashed_order"); | ||||
|     Saga.Result goodOrder = sagaOrchestrator.execute("good_order"); | ||||
|     Saga.Result badOrder = sagaOrchestrator.execute("bad_order"); | ||||
|     Saga.Result crashedOrder = sagaOrchestrator.execute("crashed_order"); | ||||
|  | ||||
|         logger.info("orders: goodOrder is {}, badOrder is {},crashedOrder is {}",goodOrder,badOrder,crashedOrder); | ||||
|     } | ||||
|     LOGGER.info("orders: goodOrder is {}, badOrder is {},crashedOrder is {}", goodOrder, badOrder, crashedOrder); | ||||
|   } | ||||
|  | ||||
|  | ||||
|     private static Saga newSaga() { | ||||
|         return Saga | ||||
|                 .create() | ||||
|                 .chapter("init an order") | ||||
|                 .chapter("booking a Fly") | ||||
|                 .chapter("booking a Hotel") | ||||
|                 .chapter("withdrawing Money"); | ||||
|     } | ||||
|   private static Saga newSaga() { | ||||
|     return Saga | ||||
|         .create() | ||||
|         .chapter("init an order") | ||||
|         .chapter("booking a Fly") | ||||
|         .chapter("booking a Hotel") | ||||
|         .chapter("withdrawing Money"); | ||||
|   } | ||||
|  | ||||
|     private static ServiceDiscoveryService serviceDiscovery() { | ||||
|         return | ||||
|                 new ServiceDiscoveryService() | ||||
|                         .discover(new OrderService()) | ||||
|                         .discover(new FlyBookingService()) | ||||
|                         .discover(new HotelBookingService()) | ||||
|                         .discover(new WithdrawMoneyService()); | ||||
|     } | ||||
|   private static ServiceDiscoveryService serviceDiscovery() { | ||||
|     return | ||||
|         new ServiceDiscoveryService() | ||||
|             .discover(new OrderService()) | ||||
|             .discover(new FlyBookingService()) | ||||
|             .discover(new HotelBookingService()) | ||||
|             .discover(new WithdrawMoneyService()); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -34,107 +34,112 @@ import static com.iluwatar.saga.orchestration.Saga.Result.*; | ||||
|  * the participant services to execute local transactions based on events. | ||||
|  */ | ||||
| public class SagaOrchestrator { | ||||
|     private static final Logger logger = LoggerFactory.getLogger(SagaOrchestrator.class); | ||||
|     private final Saga saga; | ||||
|     private final ServiceDiscoveryService sd; | ||||
|     private final CurrentState state; | ||||
|   private static final Logger LOGGER = LoggerFactory.getLogger(SagaOrchestrator.class); | ||||
|   private final Saga saga; | ||||
|   private final ServiceDiscoveryService sd; | ||||
|   private final CurrentState state; | ||||
|  | ||||
|  | ||||
|     public SagaOrchestrator(Saga saga, ServiceDiscoveryService sd) { | ||||
|         this.saga = saga; | ||||
|         this.sd = sd; | ||||
|         this.state = new CurrentState(); | ||||
|   /** | ||||
|    * Create a new service to orchetrate sagas | ||||
|    * @param saga saga to process | ||||
|    * @param sd service discovery @see {@link ServiceDiscoveryService} | ||||
|    */ | ||||
|   public SagaOrchestrator(Saga saga, ServiceDiscoveryService sd) { | ||||
|     this.saga = saga; | ||||
|     this.sd = sd; | ||||
|     this.state = new CurrentState(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * pipeline to execute saga process/story | ||||
|    * | ||||
|    * @param value incoming value | ||||
|    * @param <K>   type for incoming value | ||||
|    * @return result @see {@link Saga.Result} | ||||
|    */ | ||||
|   @SuppressWarnings("unchecked") | ||||
|   public <K> Saga.Result execute(K value) { | ||||
|     state.cleanUp(); | ||||
|     LOGGER.info(" The new saga is about to start"); | ||||
|     Saga.Result result = FINISHED; | ||||
|     K tempVal = value; | ||||
|  | ||||
|     while (true) { | ||||
|       int next = state.current(); | ||||
|       Saga.Chapter ch = saga.get(next); | ||||
|       Optional<OrchestrationChapter> srvOpt = sd.find(ch.name); | ||||
|  | ||||
|       if (!srvOpt.isPresent()) { | ||||
|         state.directionToBack(); | ||||
|         state.back(); | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       OrchestrationChapter srv = srvOpt.get(); | ||||
|  | ||||
|       if (state.isForward()) { | ||||
|         ChapterResult processRes = srv.process(tempVal); | ||||
|         if (processRes.isSuccess()) { | ||||
|           next = state.forward(); | ||||
|           tempVal = (K) processRes.getValue(); | ||||
|         } else { | ||||
|           state.directionToBack(); | ||||
|         } | ||||
|       } else { | ||||
|         ChapterResult rlRes = srv.rollback(tempVal); | ||||
|         if (rlRes.isSuccess()) { | ||||
|           next = state.back(); | ||||
|           tempVal = (K) rlRes.getValue(); | ||||
|         } else { | ||||
|           result = CRASHED; | ||||
|           next = state.back(); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|  | ||||
|       if (!saga.isPresent(next)) { | ||||
|         return state.isForward() ? FINISHED : result == CRASHED ? CRASHED : ROLLBACK; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * pipeline to execute saga process/story | ||||
|      * | ||||
|      * @param value incoming value | ||||
|      * @param <K> type for incoming value | ||||
|      * @return result @see {@link Saga.Result} | ||||
|      */ | ||||
|     @SuppressWarnings("unchecked") | ||||
|     public <K> Saga.Result execute(K value) { | ||||
|         state.cleanUp(); | ||||
|         logger.info(" The new saga is about to start"); | ||||
|         Saga.Result result = FINISHED; | ||||
|         K tempVal = value; | ||||
|  | ||||
|         while (true) { | ||||
|             int next = state.current(); | ||||
|             Saga.Chapter ch = saga.get(next); | ||||
|             Optional<Chapter> srvOpt = sd.find(ch.name); | ||||
|  | ||||
|             if (!srvOpt.isPresent()) { | ||||
|                 state.directionToBack(); | ||||
|                 state.back(); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             Chapter srv = srvOpt.get(); | ||||
|  | ||||
|             if (state.isForward()) { | ||||
|                 ChapterResult processRes = srv.process(tempVal); | ||||
|                 if (processRes.isSuccess()) { | ||||
|                     next = state.forward(); | ||||
|                     tempVal = (K) processRes.getValue(); | ||||
|                 } else { | ||||
|                     state.directionToBack(); | ||||
|                 } | ||||
|             } else { | ||||
|                 ChapterResult rlRes = srv.rollback(tempVal); | ||||
|                 if (rlRes.isSuccess()) { | ||||
|                     next = state.back(); | ||||
|                     tempVal = (K) rlRes.getValue(); | ||||
|                 } else { | ||||
|                     result = CRASHED; | ||||
|                     next = state.back(); | ||||
|                 } | ||||
|             } | ||||
|   } | ||||
|  | ||||
|  | ||||
|             if (!saga.isPresent(next)) { | ||||
|                 return state.isForward() ? FINISHED : result == CRASHED ? CRASHED : ROLLBACK; | ||||
|             } | ||||
|         } | ||||
|   private static class CurrentState { | ||||
|     int currentNumber; | ||||
|     boolean isForward; | ||||
|  | ||||
|     void cleanUp() { | ||||
|       currentNumber = 0; | ||||
|       isForward = true; | ||||
|     } | ||||
|  | ||||
|     CurrentState() { | ||||
|       this.currentNumber = 0; | ||||
|       this.isForward = true; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private static class CurrentState { | ||||
|         int currentNumber; | ||||
|         boolean isForward; | ||||
|  | ||||
|         void cleanUp() { | ||||
|             currentNumber = 0; | ||||
|             isForward = true; | ||||
|         } | ||||
|  | ||||
|         CurrentState() { | ||||
|             this.currentNumber = 0; | ||||
|             this.isForward = true; | ||||
|         } | ||||
|  | ||||
|  | ||||
|         boolean isForward() { | ||||
|             return isForward; | ||||
|         } | ||||
|  | ||||
|         void directionToBack() { | ||||
|             isForward = false; | ||||
|         } | ||||
|  | ||||
|         int forward() { | ||||
|             return ++currentNumber; | ||||
|         } | ||||
|  | ||||
|         int back() { | ||||
|             return --currentNumber; | ||||
|         } | ||||
|  | ||||
|         int current() { | ||||
|             return currentNumber; | ||||
|         } | ||||
|     boolean isForward() { | ||||
|       return isForward; | ||||
|     } | ||||
|  | ||||
|     void directionToBack() { | ||||
|       isForward = false; | ||||
|     } | ||||
|  | ||||
|     int forward() { | ||||
|       return ++currentNumber; | ||||
|     } | ||||
|  | ||||
|     int back() { | ||||
|       return --currentNumber; | ||||
|     } | ||||
|  | ||||
|     int current() { | ||||
|       return currentNumber; | ||||
|     } | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -27,29 +27,30 @@ import org.slf4j.LoggerFactory; | ||||
|  | ||||
| /** | ||||
|  * Common abstraction class representing services | ||||
|  * implementing a general contract @see {@link Chapter} | ||||
|  * implementing a general contract @see {@link OrchestrationChapter} | ||||
|  * | ||||
|  * @param <K> type of incoming param | ||||
|  */ | ||||
| public abstract class Service<K> implements Chapter<K> { | ||||
|     protected static final Logger logger = LoggerFactory.getLogger(Service.class); | ||||
| public abstract class Service<K> implements OrchestrationChapter<K> { | ||||
|   protected static final Logger LOGGER = LoggerFactory.getLogger(Service.class); | ||||
|  | ||||
|     @Override | ||||
|     public abstract String getName(); | ||||
|   @Override | ||||
|   public abstract String getName(); | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     public ChapterResult<K> process(K value) { | ||||
|         logger.info("The chapter '{}' has been started. The data {} has been stored or calculated successfully", | ||||
|                 getName(),value); | ||||
|         return ChapterResult.success(value); | ||||
|     } | ||||
|   @Override | ||||
|   public ChapterResult<K> process(K value) { | ||||
|     LOGGER.info("The chapter '{}' has been started. The data {} has been stored or calculated successfully", | ||||
|         getName(), value); | ||||
|     return ChapterResult.success(value); | ||||
|   } | ||||
|  | ||||
|     @Override | ||||
|     public ChapterResult<K> rollback(K value) { | ||||
|         logger.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully", | ||||
|                 getName(),value); | ||||
|         return ChapterResult.success(value); | ||||
|     } | ||||
|   @Override | ||||
|   public ChapterResult<K> rollback(K value) { | ||||
|     LOGGER.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully", | ||||
|         getName(), value); | ||||
|     return ChapterResult.success(value); | ||||
|   } | ||||
|  | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -30,20 +30,20 @@ import java.util.Optional; | ||||
|  * The class representing a service discovery pattern. | ||||
|  */ | ||||
| public class ServiceDiscoveryService { | ||||
|     private Map<String, Chapter<?>> services; | ||||
|   private Map<String, OrchestrationChapter<?>> services; | ||||
|  | ||||
|     public Optional<Chapter> find(String service) { | ||||
|         return Optional.ofNullable(services.getOrDefault(service, null)); | ||||
|     } | ||||
|   public Optional<OrchestrationChapter> find(String service) { | ||||
|     return Optional.ofNullable(services.getOrDefault(service, null)); | ||||
|   } | ||||
|  | ||||
|     public ServiceDiscoveryService discover(Chapter<?> chapterService) { | ||||
|         services.put(chapterService.getName(), chapterService); | ||||
|         return this; | ||||
|     } | ||||
|   public ServiceDiscoveryService discover(OrchestrationChapter<?> orchestrationChapterService) { | ||||
|     services.put(orchestrationChapterService.getName(), orchestrationChapterService); | ||||
|     return this; | ||||
|   } | ||||
|  | ||||
|     public ServiceDiscoveryService() { | ||||
|         this.services = new HashMap<>(); | ||||
|     } | ||||
|   public ServiceDiscoveryService() { | ||||
|     this.services = new HashMap<>(); | ||||
|   } | ||||
|  | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -26,19 +26,19 @@ package com.iluwatar.saga.orchestration; | ||||
|  * Class representing a service to withdraw a money | ||||
|  */ | ||||
| public class WithdrawMoneyService extends Service<String> { | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return "withdrawing Money"; | ||||
|     } | ||||
|   @Override | ||||
|   public String getName() { | ||||
|     return "withdrawing Money"; | ||||
|   } | ||||
|  | ||||
|     @Override | ||||
|     public ChapterResult<String> process(String value) { | ||||
|         if (value.equals("bad_order") || value.equals("crashed_order")) { | ||||
|             logger.info("The chapter '{}' has been started. But the exception has been raised." + | ||||
|                             "The rollback is about to start", | ||||
|                     getName(), value); | ||||
|             return ChapterResult.failure(value); | ||||
|         } | ||||
|         return super.process(value); | ||||
|   @Override | ||||
|   public ChapterResult<String> process(String value) { | ||||
|     if (value.equals("bad_order") || value.equals("crashed_order")) { | ||||
|       LOGGER.info("The chapter '{}' has been started. But the exception has been raised." | ||||
|               + "The rollback is about to start", | ||||
|           getName(), value); | ||||
|       return ChapterResult.failure(value); | ||||
|     } | ||||
|     return super.process(value); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -25,11 +25,13 @@ package com.iluwatar.saga.choreography; | ||||
| import com.iluwatar.saga.orchestration.SagaApplication; | ||||
| import org.junit.Test; | ||||
|  | ||||
| import static org.junit.Assert.*; | ||||
|  | ||||
| /*** | ||||
|  * empty test | ||||
|  */ | ||||
| public class SagaApplicationTest { | ||||
|     @Test | ||||
|     public void mainTest() { | ||||
|         SagaApplication.main(new String[]{}); | ||||
|     } | ||||
|   @Test | ||||
|   public void mainTest() { | ||||
|     SagaApplication.main(new String[]{}); | ||||
|   } | ||||
| } | ||||
| @@ -25,35 +25,38 @@ package com.iluwatar.saga.choreography; | ||||
| import org.junit.Assert; | ||||
| import org.junit.Test; | ||||
|  | ||||
| /** | ||||
|  * test to check choreography saga | ||||
|  */ | ||||
| public class SagaChoreographyTest { | ||||
|  | ||||
|  | ||||
|     @Test | ||||
|     public void executeTest() { | ||||
|         ServiceDiscoveryService sd = serviceDiscovery(); | ||||
|         Chapter service = sd.findAny(); | ||||
|         Saga badOrderSaga = service.execute(newSaga("bad_order")); | ||||
|         Saga goodOrderSaga = service.execute(newSaga("good_order")); | ||||
|   @Test | ||||
|   public void executeTest() { | ||||
|     ServiceDiscoveryService sd = serviceDiscovery(); | ||||
|     ChoreographyChapter service = sd.findAny(); | ||||
|     Saga badOrderSaga = service.execute(newSaga("bad_order")); | ||||
|     Saga goodOrderSaga = service.execute(newSaga("good_order")); | ||||
|  | ||||
|         Assert.assertEquals(badOrderSaga.getResult(), Saga.SagaResult.ROLLBACKED); | ||||
|         Assert.assertEquals(goodOrderSaga.getResult(), Saga.SagaResult.FINISHED); | ||||
|     } | ||||
|     Assert.assertEquals(badOrderSaga.getResult(), Saga.SagaResult.ROLLBACKED); | ||||
|     Assert.assertEquals(goodOrderSaga.getResult(), Saga.SagaResult.FINISHED); | ||||
|   } | ||||
|  | ||||
|     private static Saga newSaga(Object value) { | ||||
|         return Saga | ||||
|                 .create() | ||||
|                 .chapter("init an order").setInValue(value) | ||||
|                 .chapter("booking a Fly") | ||||
|                 .chapter("booking a Hotel") | ||||
|                 .chapter("withdrawing Money"); | ||||
|     } | ||||
|   private static Saga newSaga(Object value) { | ||||
|     return Saga | ||||
|         .create() | ||||
|         .chapter("init an order").setInValue(value) | ||||
|         .chapter("booking a Fly") | ||||
|         .chapter("booking a Hotel") | ||||
|         .chapter("withdrawing Money"); | ||||
|   } | ||||
|  | ||||
|     private static ServiceDiscoveryService serviceDiscovery() { | ||||
|         ServiceDiscoveryService sd = new ServiceDiscoveryService(); | ||||
|         return sd | ||||
|                 .discover(new OrderService(sd)) | ||||
|                 .discover(new FlyBookingService(sd)) | ||||
|                 .discover(new HotelBookingService(sd)) | ||||
|                 .discover(new WithdrawMoneyService(sd)); | ||||
|     } | ||||
|   private static ServiceDiscoveryService serviceDiscovery() { | ||||
|     ServiceDiscoveryService sd = new ServiceDiscoveryService(); | ||||
|     return sd | ||||
|         .discover(new OrderService(sd)) | ||||
|         .discover(new FlyBookingService(sd)) | ||||
|         .discover(new HotelBookingService(sd)) | ||||
|         .discover(new WithdrawMoneyService(sd)); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -26,10 +26,13 @@ import org.junit.Test; | ||||
|  | ||||
| import static org.junit.Assert.*; | ||||
|  | ||||
| /** | ||||
|  * empty test | ||||
|  */ | ||||
| public class SagaApplicationTest { | ||||
|  | ||||
|     @Test | ||||
|     public void mainTest() { | ||||
|         SagaApplication.main(new String[]{}); | ||||
|     } | ||||
|   @Test | ||||
|   public void mainTest() { | ||||
|     SagaApplication.main(new String[]{}); | ||||
|   } | ||||
| } | ||||
| @@ -28,112 +28,118 @@ import org.junit.Test; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * test to test orchestration logic | ||||
|  */ | ||||
| public class SagaOrchestratorInternallyTest { | ||||
|  | ||||
|     private List<String> records = new ArrayList<>(); | ||||
|   private List<String> records = new ArrayList<>(); | ||||
|  | ||||
|     @Test | ||||
|     public void executeTest() { | ||||
|         SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery()); | ||||
|         Saga.Result result = sagaOrchestrator.execute(1); | ||||
|         Assert.assertEquals(result, Saga.Result.ROLLBACK); | ||||
|         Assert.assertArrayEquals( | ||||
|                 records.toArray(new String[]{}), | ||||
|                 new String[]{"+1","+2","+3","+4","-4","-3","-2","-1"}); | ||||
|   @Test | ||||
|   public void executeTest() { | ||||
|     SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery()); | ||||
|     Saga.Result result = sagaOrchestrator.execute(1); | ||||
|     Assert.assertEquals(result, Saga.Result.ROLLBACK); | ||||
|     Assert.assertArrayEquals( | ||||
|         records.toArray(new String[]{}), | ||||
|         new String[]{"+1", "+2", "+3", "+4", "-4", "-3", "-2", "-1"}); | ||||
|   } | ||||
|  | ||||
|   private static Saga newSaga() { | ||||
|     return Saga | ||||
|         .create() | ||||
|         .chapter("1") | ||||
|         .chapter("2") | ||||
|         .chapter("3") | ||||
|         .chapter("4"); | ||||
|   } | ||||
|  | ||||
|   private ServiceDiscoveryService serviceDiscovery() { | ||||
|     return | ||||
|         new ServiceDiscoveryService() | ||||
|             .discover(new Service1()) | ||||
|             .discover(new Service2()) | ||||
|             .discover(new Service3()) | ||||
|             .discover(new Service4()); | ||||
|   } | ||||
|  | ||||
|   class Service1 extends Service<Integer> { | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|       return "1"; | ||||
|     } | ||||
|  | ||||
|     private static Saga newSaga() { | ||||
|         return Saga | ||||
|                 .create() | ||||
|                 .chapter("1") | ||||
|                 .chapter("2") | ||||
|                 .chapter("3") | ||||
|                 .chapter("4"); | ||||
|     @Override | ||||
|     public ChapterResult<Integer> process(Integer value) { | ||||
|       records.add("+1"); | ||||
|       return ChapterResult.success(value); | ||||
|     } | ||||
|  | ||||
|     private ServiceDiscoveryService serviceDiscovery() { | ||||
|         return | ||||
|                 new ServiceDiscoveryService() | ||||
|                         .discover(new Service1()) | ||||
|                         .discover(new Service2()) | ||||
|                         .discover(new Service3()) | ||||
|                         .discover(new Service4()); | ||||
|     @Override | ||||
|     public ChapterResult<Integer> rollback(Integer value) { | ||||
|       records.add("-1"); | ||||
|       return ChapterResult.success(value); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   class Service2 extends Service<Integer> { | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|       return "2"; | ||||
|     } | ||||
|  | ||||
|     class Service1 extends Service<Integer> { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { | ||||
|             return "1"; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public ChapterResult<Integer> process(Integer value) { | ||||
|             records.add("+1"); | ||||
|             return ChapterResult.success(value); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public ChapterResult<Integer> rollback(Integer value) { | ||||
|             records.add("-1"); | ||||
|             return ChapterResult.success(value); | ||||
|         } | ||||
|     @Override | ||||
|     public ChapterResult<Integer> process(Integer value) { | ||||
|       records.add("+2"); | ||||
|       return ChapterResult.success(value); | ||||
|     } | ||||
|  | ||||
|     class Service2 extends Service<Integer> { | ||||
|     @Override | ||||
|     public ChapterResult<Integer> rollback(Integer value) { | ||||
|       records.add("-2"); | ||||
|       return ChapterResult.success(value); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { | ||||
|             return "2"; | ||||
|         } | ||||
|         @Override | ||||
|         public ChapterResult<Integer> process(Integer value) { | ||||
|             records.add("+2"); | ||||
|             return ChapterResult.success(value); | ||||
|         } | ||||
|   class Service3 extends Service<Integer> { | ||||
|  | ||||
|         @Override | ||||
|         public ChapterResult<Integer> rollback(Integer value) { | ||||
|             records.add("-2"); | ||||
|             return ChapterResult.success(value); | ||||
|         } | ||||
|     @Override | ||||
|     public String getName() { | ||||
|       return "3"; | ||||
|     } | ||||
|  | ||||
|     class Service3 extends Service<Integer> { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { | ||||
|             return "3"; | ||||
|         } | ||||
|         @Override | ||||
|         public ChapterResult<Integer> process(Integer value) { | ||||
|             records.add("+3"); | ||||
|             return ChapterResult.success(value); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public ChapterResult<Integer> rollback(Integer value) { | ||||
|             records.add("-3"); | ||||
|             return ChapterResult.success(value); | ||||
|         } | ||||
|     @Override | ||||
|     public ChapterResult<Integer> process(Integer value) { | ||||
|       records.add("+3"); | ||||
|       return ChapterResult.success(value); | ||||
|     } | ||||
|  | ||||
|     class Service4 extends Service<Integer> { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { | ||||
|             return "4"; | ||||
|         } | ||||
|         @Override | ||||
|         public ChapterResult<Integer> process(Integer value) { | ||||
|             records.add("+4"); | ||||
|             return ChapterResult.failure(value); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public ChapterResult<Integer> rollback(Integer value) { | ||||
|             records.add("-4"); | ||||
|             return ChapterResult.success(value); | ||||
|         } | ||||
|     @Override | ||||
|     public ChapterResult<Integer> rollback(Integer value) { | ||||
|       records.add("-3"); | ||||
|       return ChapterResult.success(value); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   class Service4 extends Service<Integer> { | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|       return "4"; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ChapterResult<Integer> process(Integer value) { | ||||
|       records.add("+4"); | ||||
|       return ChapterResult.failure(value); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ChapterResult<Integer> rollback(Integer value) { | ||||
|       records.add("-4"); | ||||
|       return ChapterResult.success(value); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -25,33 +25,36 @@ package com.iluwatar.saga.orchestration; | ||||
| import org.junit.Assert; | ||||
| import org.junit.Test; | ||||
|  | ||||
| /** | ||||
|  * test to check general logic | ||||
|  */ | ||||
| public class SagaOrchestratorTest { | ||||
|  | ||||
|     @Test | ||||
|     public void execute() { | ||||
|         SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery()); | ||||
|         Saga.Result badOrder = sagaOrchestrator.execute("bad_order"); | ||||
|         Saga.Result crashedOrder = sagaOrchestrator.execute("crashed_order"); | ||||
|   @Test | ||||
|   public void execute() { | ||||
|     SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery()); | ||||
|     Saga.Result badOrder = sagaOrchestrator.execute("bad_order"); | ||||
|     Saga.Result crashedOrder = sagaOrchestrator.execute("crashed_order"); | ||||
|  | ||||
|         Assert.assertEquals(badOrder, Saga.Result.ROLLBACK); | ||||
|         Assert.assertEquals(crashedOrder, Saga.Result.CRASHED); | ||||
|     } | ||||
|     Assert.assertEquals(badOrder, Saga.Result.ROLLBACK); | ||||
|     Assert.assertEquals(crashedOrder, Saga.Result.CRASHED); | ||||
|   } | ||||
|  | ||||
|     private static Saga newSaga() { | ||||
|         return Saga | ||||
|                 .create() | ||||
|                 .chapter("init an order") | ||||
|                 .chapter("booking a Fly") | ||||
|                 .chapter("booking a Hotel") | ||||
|                 .chapter("withdrawing Money"); | ||||
|     } | ||||
|   private static Saga newSaga() { | ||||
|     return Saga | ||||
|         .create() | ||||
|         .chapter("init an order") | ||||
|         .chapter("booking a Fly") | ||||
|         .chapter("booking a Hotel") | ||||
|         .chapter("withdrawing Money"); | ||||
|   } | ||||
|  | ||||
|     private static ServiceDiscoveryService serviceDiscovery() { | ||||
|         return | ||||
|                 new ServiceDiscoveryService() | ||||
|                         .discover(new OrderService()) | ||||
|                         .discover(new FlyBookingService()) | ||||
|                         .discover(new HotelBookingService()) | ||||
|                         .discover(new WithdrawMoneyService()); | ||||
|     } | ||||
|   private static ServiceDiscoveryService serviceDiscovery() { | ||||
|     return | ||||
|         new ServiceDiscoveryService() | ||||
|             .discover(new OrderService()) | ||||
|             .discover(new FlyBookingService()) | ||||
|             .discover(new HotelBookingService()) | ||||
|             .discover(new WithdrawMoneyService()); | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user