diff --git a/pom.xml b/pom.xml index 6a666caab..1ab896068 100644 --- a/pom.xml +++ b/pom.xml @@ -183,6 +183,8 @@ data-locality subclass-sandbox circuit-breaker + role-object + saga double-buffer sharding diff --git a/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java b/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java index 6d5c17c90..d6ec6bc42 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java @@ -43,6 +43,6 @@ public class InvestorRole extends CustomerRole { } public String invest() { - return String.format("Investor %s has invested %d dollars", name, amountToInvest); + return String.format("Investor %s has invested %d dollars", name, amountToInvest); } } diff --git a/saga/README.md b/saga/README.md new file mode 100644 index 000000000..546ad598b --- /dev/null +++ b/saga/README.md @@ -0,0 +1,46 @@ +--- +layout: pattern +title: Saga +folder: saga +permalink: /patterns/saga/ +categories: Behavioral +tags: + - Java + - Difficulty-Expert + - Idiom + - Distributed communication +--- + +## Also known as +This pattern has a similar goal with two-phase commit (XA transaction) + +## Intent +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 + +## Explanation +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. +There are two types of Saga: + +- Choreography-Based Saga. +In this approach, there is no central orchestrator. +Each service participating in the Saga performs their transaction and publish events. +The other services act upon those events and perform their transactions. +Also, they may or not publish other events based on the situation. + +- Orchestration-Based Saga +In this approach, there is a Saga orchestrator that manages all the transactions and directs +the participant services to execute local transactions based on events. +This orchestrator can also be though of as a Saga Manager. + +## Applicability +Use the Saga pattern, if: +- you need to perform a group of operations related to different microservices atomically +- you need to rollback changes in different places in case of failure one of the operation +- you need to take care of data consistency in different places including different databases +- you can not use 2PC(two phase commit) + +## Credits +- [Pattern: Saga](https://microservices.io/patterns/data/saga.html) \ No newline at end of file diff --git a/saga/pom.xml b/saga/pom.xml new file mode 100644 index 000000000..111f4e7b8 --- /dev/null +++ b/saga/pom.xml @@ -0,0 +1,45 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.22.0-SNAPSHOT + + + saga + + + junit + junit + test + + + + diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/ChoreographyChapter.java b/saga/src/main/java/com/iluwatar/saga/choreography/ChoreographyChapter.java new file mode 100644 index 000000000..c79a77a9a --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/ChoreographyChapter.java @@ -0,0 +1,65 @@ +/* + * 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.choreography; + + +/** + * 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 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); + + /** + * get name method. + * @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 rollback case. + * + * @param saga incoming saga + * @return result {@link Saga} + */ + Saga rollback(Saga saga); + + +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/FlyBookingService.java b/saga/src/main/java/com/iluwatar/saga/choreography/FlyBookingService.java new file mode 100644 index 000000000..4a9b1e804 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/FlyBookingService.java @@ -0,0 +1,39 @@ +/* + * 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.choreography; + + +/** + * Class representing a service to book a fly. + */ +public class FlyBookingService extends Service { + public FlyBookingService(ServiceDiscoveryService service) { + super(service); + } + + @Override + public String getName() { + return "booking a Fly"; + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/HotelBookingService.java b/saga/src/main/java/com/iluwatar/saga/choreography/HotelBookingService.java new file mode 100644 index 000000000..ee623482e --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/HotelBookingService.java @@ -0,0 +1,41 @@ +/* + * 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.choreography; + + +/** + * Class representing a service to book a hotel. + */ +public class HotelBookingService extends Service { + public HotelBookingService(ServiceDiscoveryService service) { + super(service); + } + + @Override + public String getName() { + return "booking a Hotel"; + } + + +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/OrderService.java b/saga/src/main/java/com/iluwatar/saga/choreography/OrderService.java new file mode 100644 index 000000000..19586a5a3 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/OrderService.java @@ -0,0 +1,40 @@ +/* + * 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.choreography; + + +/** + * Class representing a service to init a new order. + */ +public class OrderService extends Service { + + public OrderService(ServiceDiscoveryService service) { + super(service); + } + + @Override + public String getName() { + return "init an order"; + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java b/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java new file mode 100644 index 000000000..b591adaf2 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java @@ -0,0 +1,217 @@ +/* + * 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.choreography; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Saga representation. + * Saga consists of chapters. + * Every ChoreographyChapter is executed a certain service. + */ +public class Saga { + + private List chapters; + private int pos; + private boolean forward; + private boolean finished; + + + 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; + } + + return SagaResult.PROGRESS; + } + + /** + * 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; + } + + + private Saga() { + this.chapters = new ArrayList<>(); + this.pos = 0; + this.forward = true; + this.finished = false; + } + + Chapter getCurrent() { + return chapters.get(pos); + } + + + 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; + } + + 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 void setResult(ChapterResult result) { + this.result = result; + } + + /** + * the result for chapter is good. + * @return true if is good otherwise bad + */ + public boolean isSuccess() { + return result == ChapterResult.SUCCESS; + } + } + + + /** + * 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 + + '}'; + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/SagaApplication.java b/saga/src/main/java/com/iluwatar/saga/choreography/SagaApplication.java new file mode 100644 index 000000000..d8844c864 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/SagaApplication.java @@ -0,0 +1,84 @@ +/* + * 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.choreography; + +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 are no mediators or orchestrators services. + * All chapters are handled and moved by services manually. + * + *

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) + * + * @see com.iluwatar.saga.choreography.Saga + * @see Service + */ +public class SagaApplication { + private static final Logger LOGGER = LoggerFactory.getLogger(SagaApplication.class); + + /** + * 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 ServiceDiscoveryService serviceDiscovery() { + ServiceDiscoveryService sd = new ServiceDiscoveryService(); + return sd + .discover(new OrderService(sd)) + .discover(new FlyBookingService(sd)) + .discover(new HotelBookingService(sd)) + .discover(new WithdrawMoneyService(sd)); + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/Service.java b/saga/src/main/java/com/iluwatar/saga/choreography/Service.java new file mode 100644 index 000000000..2e932b528 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/Service.java @@ -0,0 +1,113 @@ +/* + * 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.choreography; + +import java.util.function.Supplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Common abstraction class representing services. + * implementing a general contract @see {@link ChoreographyChapter} + */ +public abstract class Service implements ChoreographyChapter { + protected static final Logger LOGGER = LoggerFactory.getLogger(Service.class); + + private final ServiceDiscoveryService sd; + + 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); + } + Saga finalNextSaga = nextSaga; + + return sd.find(chapterName).map(ch -> ch.execute(finalNextSaga)) + .orElseThrow(serviceNotFoundException(chapterName)); + } + + private Supplier 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; + } + +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java b/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java new file mode 100644 index 000000000..a616ff4a5 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java @@ -0,0 +1,61 @@ +/* + * 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.choreography; + +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +/** + * The class representing a service discovery pattern. + */ +public class ServiceDiscoveryService { + private Map services; + + /** + * find any service. + * + * @return any service + * @throws NoSuchElementException if no elements further + */ + public ChoreographyChapter findAny() { + return services.values().iterator().next(); + } + + public Optional find(String service) { + return Optional.ofNullable(services.getOrDefault(service, null)); + } + + public ServiceDiscoveryService discover(ChoreographyChapter chapterService) { + services.put(chapterService.getName(), chapterService); + return this; + } + + public ServiceDiscoveryService() { + this.services = new HashMap<>(); + } + + +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/WithdrawMoneyService.java b/saga/src/main/java/com/iluwatar/saga/choreography/WithdrawMoneyService.java new file mode 100644 index 000000000..637e8ac1b --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/WithdrawMoneyService.java @@ -0,0 +1,53 @@ +/* + * 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.choreography; + +/** + * Class representing a service to withdraw a money. + */ +public class WithdrawMoneyService extends Service { + + public WithdrawMoneyService(ServiceDiscoveryService service) { + super(service); + } + + @Override + public String getName() { + return "withdrawing Money"; + } + + @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); + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java b/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java new file mode 100644 index 000000000..ef34ddb98 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java @@ -0,0 +1,62 @@ +/* + * 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 { + private K value; + private 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); + } + + /** + * state for chapter. + */ + public enum State { + SUCCESS, 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 new file mode 100644 index 000000000..23b612957 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java @@ -0,0 +1,34 @@ +/* + * 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() { + return "booking a Fly"; + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java new file mode 100644 index 000000000..2d6ba1389 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java @@ -0,0 +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; + +/** + * 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/OrchestrationChapter.java b/saga/src/main/java/com/iluwatar/saga/orchestration/OrchestrationChapter.java new file mode 100644 index 000000000..7e9e3581f --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/OrchestrationChapter.java @@ -0,0 +1,55 @@ +/* + * 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; + +/** + * ChoreographyChapter is an interface representing a contract for an external service. + * + * @param is type for passing params + */ +public interface OrchestrationChapter { + + /** + * method get name. + * @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/OrderService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java new file mode 100644 index 000000000..c42a9d7a1 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java @@ -0,0 +1,34 @@ +/* + * 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() { + return "init an order"; + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java b/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java new file mode 100644 index 000000000..0d53362aa --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java @@ -0,0 +1,84 @@ +/* + * 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.ArrayList; +import java.util.List; + +/** + * Saga representation. + * Saga consists of chapters. + * Every ChoreographyChapter is executed by a certain service. + */ +public class Saga { + + private List chapters; + + + 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 String getName() { + return name; + } + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java new file mode 100644 index 000000000..830f5e653 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java @@ -0,0 +1,87 @@ +/* + * 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; + +/** + * 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. + * 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) + * + * @see Saga + * @see SagaOrchestrator + * @see Service + */ +public class SagaApplication { + private static final Logger LOGGER = LoggerFactory.getLogger(SagaApplication.class); + + /** + * 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"); + + 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 ServiceDiscoveryService serviceDiscovery() { + return + new ServiceDiscoveryService() + .discover(new OrderService()) + .discover(new FlyBookingService()) + .discover(new HotelBookingService()) + .discover(new WithdrawMoneyService()); + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java new file mode 100644 index 000000000..beec37655 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java @@ -0,0 +1,148 @@ +/* + * 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 static com.iluwatar.saga.orchestration.Saga.Result.CRASHED; +import static com.iluwatar.saga.orchestration.Saga.Result.FINISHED; +import static com.iluwatar.saga.orchestration.Saga.Result.ROLLBACK; + +import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The orchestrator that manages all the transactions and directs + * 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; + + + /** + * 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 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; + } + + 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; + } + } + + } + + + 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 new file mode 100644 index 000000000..d2b065201 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java @@ -0,0 +1,59 @@ +/* + * 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; + +/** + * Common abstraction class representing services. + * implementing a general contract @see {@link OrchestrationChapter} + * + * @param type of incoming param + */ +public abstract class Service implements OrchestrationChapter { + protected static final Logger LOGGER = LoggerFactory.getLogger(Service.class); + + @Override + public abstract String getName(); + + + @Override + public ChapterResult 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 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); + } + + +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java new file mode 100644 index 000000000..dbc6e7eb5 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java @@ -0,0 +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; + +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) { + return Optional.ofNullable(services.getOrDefault(service, null)); + } + + public ServiceDiscoveryService discover(OrchestrationChapter orchestrationChapterService) { + services.put(orchestrationChapterService.getName(), orchestrationChapterService); + return this; + } + + public ServiceDiscoveryService() { + this.services = new HashMap<>(); + } + + +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java new file mode 100644 index 000000000..7eb5634ef --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java @@ -0,0 +1,45 @@ +/* + * 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() { + return "withdrawing Money"; + } + + @Override + public ChapterResult 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); + } +} diff --git a/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java b/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java new file mode 100644 index 000000000..d7a2a34b2 --- /dev/null +++ b/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java @@ -0,0 +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.choreography; + +import com.iluwatar.saga.orchestration.SagaApplication; +import org.junit.Test; + + +/*** + * empty test + */ +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/choreography/SagaChoreographyTest.java b/saga/src/test/java/com/iluwatar/saga/choreography/SagaChoreographyTest.java new file mode 100644 index 000000000..4300d4057 --- /dev/null +++ b/saga/src/test/java/com/iluwatar/saga/choreography/SagaChoreographyTest.java @@ -0,0 +1,62 @@ +/* + * 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.choreography; + +import org.junit.Assert; +import org.junit.Test; + +/** + * test to check choreography saga + */ +public class SagaChoreographyTest { + + + @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); + } + + 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)); + } +} 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..aa3c8773f --- /dev/null +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java @@ -0,0 +1,38 @@ +/* + * 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.junit.Test; + +import static org.junit.Assert.*; + +/** + * empty test + */ +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..a93bf5280 --- /dev/null +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java @@ -0,0 +1,145 @@ +/* + * 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.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * test to test orchestration logic + */ +public class SagaOrchestratorInternallyTest { + + private List 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"}); + } + + 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..d3522418c --- /dev/null +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java @@ -0,0 +1,60 @@ +/* + * 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.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"); + + 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