From 3a4677c56d19a1b5d1044cf32b535f729141ec99 Mon Sep 17 00:00:00 2001 From: Besok Date: Sat, 2 Nov 2019 20:52:59 +0000 Subject: [PATCH] add orchestrator --- .../iluwatar/saga/orchestration/Chapter.java | 17 ++- .../saga/orchestration/ChapterResult.java | 41 ++++- .../saga/orchestration/FlyBookingService.java | 25 ++++ .../orchestration/HotelBookingService.java | 42 ++++++ .../saga/orchestration/OrderService.java | 25 ++++ .../com/iluwatar/saga/orchestration/Saga.java | 42 +++--- .../saga/orchestration/SagaApplication.java | 42 +++++- .../saga/orchestration/SagaException.java | 29 ---- .../saga/orchestration/SagaOrchestrator.java | 141 ++++++++++++++++-- .../iluwatar/saga/orchestration/Service.java | 42 ++++-- .../ServiceDiscoveryService.java | 5 +- .../iluwatar/saga/orchestration/Utility.java | 16 -- .../orchestration/WithdrawMoneyService.java | 30 +++- .../orchestration/SagaApplicationTest.java | 13 ++ .../SagaOrchestratorInternallyTest.java | 117 +++++++++++++++ .../orchestration/SagaOrchestratorTest.java | 37 +++++ 16 files changed, 555 insertions(+), 109 deletions(-) delete mode 100644 saga/src/main/java/com/iluwatar/saga/orchestration/SagaException.java delete mode 100644 saga/src/main/java/com/iluwatar/saga/orchestration/Utility.java create mode 100644 saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java create mode 100644 saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java create mode 100644 saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/Chapter.java b/saga/src/main/java/com/iluwatar/saga/orchestration/Chapter.java index 9b1e1feac..961a88937 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/Chapter.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/Chapter.java @@ -23,13 +23,28 @@ package com.iluwatar.saga.orchestration; /** - * Chapter including into Saga + * Chapter is an interface representing a contract for an external service. + * @param is type for passing params */ public interface Chapter { + /** + * @return service name. + */ String getName(); + /** + * The operation executed in general case. + * @param value incoming value + * @return result {@link ChapterResult} + */ ChapterResult process(K value); + + /** + * The operation executed in rollback case. + * @param value incoming value + * @return result {@link ChapterResult} + */ ChapterResult rollback(K value); } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java b/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java index ee5ae8ba1..eae95b339 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java @@ -1,17 +1,52 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.saga.orchestration; +/** + * Executing result for chapter + * @param incoming value + */ public class ChapterResult { - K value; - State state; + private K value; + private State state; - public ChapterResult(K value, State state) { + public K getValue() { + return value; + } + + ChapterResult(K value, State state) { this.value = value; this.state = state; } + public boolean isSuccess(){ + return state == State.SUCCESS; + } + public static ChapterResult success(K val) { return new ChapterResult<>(val, State.SUCCESS); } + public static ChapterResult failure(K val) { return new ChapterResult<>(val, State.FAILURE); } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java index 7a78b5f17..6cb8479c0 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java @@ -1,5 +1,30 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.saga.orchestration; +/** + * Class representing a service to book a fly + */ public class FlyBookingService extends Service { @Override public String getName() { diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java index 8f294cd40..e21046328 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java @@ -1,8 +1,50 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.saga.orchestration; +/** + * Class representing a service to book a hotel + */ public class HotelBookingService extends Service { @Override public String getName() { return "booking a Hotel"; } + + + @Override + public ChapterResult 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); + } } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java index d5d79ac41..6edd94ace 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java @@ -1,5 +1,30 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.saga.orchestration; +/** + * Class representing a service to init a new order. + */ public class OrderService extends Service { @Override public String getName() { diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java b/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java index b74d68959..c21435f20 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java @@ -25,56 +25,50 @@ package com.iluwatar.saga.orchestration; import java.util.ArrayList; import java.util.List; +/** + * Saga representation. + * Saca consists of chapters. + * Every Chapter is executed a certain service. + */ public class Saga { private List chapters; - private Result result; public Saga() { this.chapters = new ArrayList<>(); - this.result = Result.INPROCESS; } - public void setResult(Result result) { - this.result = result; - } - public boolean isSuccess(){ - return result == Result.FINISHED; - } - public boolean isFinished(){ - return result != Result.INPROCESS; - } - - public Saga chapter(String name, int timeoutInSec) { - this.chapters.add(new Chapter(name, timeoutInSec)); + public Saga chapter(String name) { + this.chapters.add(new Chapter(name)); return this; } + public Chapter get(int idx) { - if (chapters.size() < idx || idx < 0) { - throw new SagaException("idx shoud be less then ch size or more then 0"); - } 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,CANCELED, INPROCESS; + public enum Result { + FINISHED, ROLLBACK, CRASHED } - private static class Chapter { - String name; - int timeoutInSec; - public Chapter(String name, int timeoutInSec) { + public static class Chapter { + String name; + + public Chapter(String name) { this.name = name; - this.timeoutInSec = timeoutInSec; } + } } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java index 154f97bab..71db1a7bf 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java @@ -24,22 +24,48 @@ package com.iluwatar.saga.orchestration; +import org.slf4j.Logger; +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 + * + * 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 + * the participant services to execute local transactions based on events. + * + * @see Saga + * @see SagaOrchestrator + * @see Service + */ public class SagaApplication { + private static final Logger logger = LoggerFactory.getLogger(SagaApplication.class); + public static void main(String[] args) { + SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery()); + Saga.Result goodOrder = sagaOrchestrator.execute("god_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); } - - private Saga createSaga() { - return Saga.create() - .chapter("init an order",10) - .chapter("booking a Fly",10) - .chapter("booking a Hotel",10) - .chapter("withdrawing Money",10); + 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 sd() { + private static ServiceDiscoveryService serviceDiscovery() { return new ServiceDiscoveryService() .discover(new OrderService()) diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaException.java b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaException.java deleted file mode 100644 index 0055bb85e..000000000 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaException.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.saga.orchestration; - -public class SagaException extends RuntimeException { - public SagaException(String message) { - super(message); - } -} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java index edc94bb22..34ad29b26 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java @@ -1,30 +1,139 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.saga.orchestration; -import java.util.Objects; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.Optional; + +import static com.iluwatar.saga.orchestration.Saga.Result.*; + +/** + * The orchestrator that manages all the transactions and directs + * the participant services to execute local transactions based on events. + */ public class SagaOrchestrator { - - private ExecutorService executor; - private AtomicBoolean isNormalFlow; - private AtomicBoolean isFinished; - private AtomicInteger currentState; + 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) { + + public SagaOrchestrator(Saga saga, ServiceDiscoveryService sd) { this.saga = saga; - this.isNormalFlow = new AtomicBoolean(true); - this.isFinished = new AtomicBoolean(false); - this.currentState = new AtomicInteger(0); - this.executor = Executors.newFixedThreadPool(20); + this.sd = sd; + this.state = new CurrentState(); } - public Saga.Result kickOff(K value) { + /** + * + * @param value incoming value + * @param type for incoming value + * @return result @see {@link Saga.Result} + */ + @SuppressWarnings("unchecked") + public 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 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; + } + + + boolean isForward() { + return isForward; + } + + void directionToBack() { + isForward = false; + } + + int forward() { + return ++currentNumber; + } + + int back() { + return --currentNumber; + } + + int current() { + return currentNumber; + } + } + } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java b/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java index 31a2ab702..20b34f55a 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java @@ -1,12 +1,37 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.saga.orchestration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static com.iluwatar.saga.orchestration.Utility.sleepInSec; - +/** + * Common abstraction class representing services + * implementing a general contract @see {@link Chapter} + * @param type of incoming param + */ public abstract class Service implements Chapter { - private static final Logger logger = LoggerFactory.getLogger(Service.class); + protected static final Logger logger = LoggerFactory.getLogger(Service.class); @Override public abstract String getName(); @@ -14,18 +39,15 @@ public abstract class Service implements Chapter { @Override public ChapterResult process(K value) { - logger.info("The chapter about {} is starting", getName()); - logger.info("storing or calculating a data {} to db", value); - sleepInSec(1); - logger.info("the data {} has been stored or calculated successfully", 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 rollback(K value) { - logger.info("Something is going wrong hence The chapter about {} is starting the rollback procedure", getName()); - sleepInSec(1); - logger.info("the data {} has been rollbacked successfully", value); + logger.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully", + getName(),value); return ChapterResult.success(value); } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java index 522f3f944..652740051 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java @@ -26,10 +26,13 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; +/** + * The class representing a service discovery pattern. + */ public class ServiceDiscoveryService { private Map> services; - public Optional> find(String service) { + public Optional find(String service) { return Optional.ofNullable(services.getOrDefault(service, null)); } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/Utility.java b/saga/src/main/java/com/iluwatar/saga/orchestration/Utility.java deleted file mode 100644 index bf5e902c0..000000000 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/Utility.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.iluwatar.saga.orchestration; - -public class Utility { - private Utility() { - } - - public static void sleepInSec(int sec){ - try { - Thread.sleep(sec*1000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - -} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java index 25c7710dc..dad15cec3 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java @@ -1,5 +1,30 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.saga.orchestration; +/** + * Class representing a service to withdraw a money + */ public class WithdrawMoneyService extends Service { @Override public String getName() { @@ -8,7 +33,10 @@ public class WithdrawMoneyService extends Service { @Override public ChapterResult process(String value) { - if(value.equals("no-money")){ + 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); diff --git a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java new file mode 100644 index 000000000..6950158aa --- /dev/null +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java @@ -0,0 +1,13 @@ +package com.iluwatar.saga.orchestration; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class SagaApplicationTest { + + @Test + public void mainTest() { + SagaApplication.main(new String[]{}); + } +} \ No newline at end of file diff --git a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java new file mode 100644 index 000000000..b8377cebf --- /dev/null +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java @@ -0,0 +1,117 @@ +package com.iluwatar.saga.orchestration; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class SagaOrchestratorInternallyTest { + + private List records = new ArrayList<>(); + + @Test + public void execute() { + 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 { + + @Override + public String getName() { + return "1"; + } + + @Override + public ChapterResult process(Integer value) { + records.add("+1"); + return ChapterResult.success(value); + } + + @Override + public ChapterResult rollback(Integer value) { + records.add("-1"); + return ChapterResult.success(value); + } + } + + class Service2 extends Service { + + @Override + public String getName() { + return "2"; + } + @Override + public ChapterResult process(Integer value) { + records.add("+2"); + return ChapterResult.success(value); + } + + @Override + public ChapterResult rollback(Integer value) { + records.add("-2"); + return ChapterResult.success(value); + } + } + + class Service3 extends Service { + + @Override + public String getName() { + return "3"; + } + @Override + public ChapterResult process(Integer value) { + records.add("+3"); + return ChapterResult.success(value); + } + + @Override + public ChapterResult rollback(Integer value) { + records.add("-3"); + return ChapterResult.success(value); + } + } + + class Service4 extends Service { + + @Override + public String getName() { + return "4"; + } + @Override + public ChapterResult process(Integer value) { + records.add("+4"); + return ChapterResult.failure(value); + } + + @Override + public ChapterResult rollback(Integer value) { + records.add("-4"); + return ChapterResult.success(value); + } + } +} \ No newline at end of file diff --git a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java new file mode 100644 index 000000000..8558b1f5a --- /dev/null +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java @@ -0,0 +1,37 @@ +package com.iluwatar.saga.orchestration; + +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.*; + +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"); + + 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 ServiceDiscoveryService serviceDiscovery() { + return + new ServiceDiscoveryService() + .discover(new OrderService()) + .discover(new FlyBookingService()) + .discover(new HotelBookingService()) + .discover(new WithdrawMoneyService()); + } +} \ No newline at end of file