add orchestrator
This commit is contained in:
@ -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 <K> is type for passing params
|
||||
*/
|
||||
public interface Chapter<K> {
|
||||
|
||||
/**
|
||||
* @return service name.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* The operation executed in general case.
|
||||
* @param value incoming value
|
||||
* @return result {@link ChapterResult}
|
||||
*/
|
||||
ChapterResult<K> process(K value);
|
||||
|
||||
/**
|
||||
* The operation executed in rollback case.
|
||||
* @param value incoming value
|
||||
* @return result {@link ChapterResult}
|
||||
*/
|
||||
ChapterResult<K> rollback(K value);
|
||||
|
||||
}
|
||||
|
@ -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 <K> incoming value
|
||||
*/
|
||||
public class ChapterResult<K> {
|
||||
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 <K> ChapterResult<K> success(K val) {
|
||||
return new ChapterResult<>(val, State.SUCCESS);
|
||||
}
|
||||
|
||||
public static <K> ChapterResult<K> failure(K val) {
|
||||
return new ChapterResult<>(val, State.FAILURE);
|
||||
}
|
||||
|
@ -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<String> {
|
||||
@Override
|
||||
public String getName() {
|
||||
|
@ -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<String> {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "booking a Hotel";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ChapterResult<String> rollback(String value) {
|
||||
if(value.equals("crashed_order")){
|
||||
logger.info("The Rollback for a chapter '{}' has been started. " +
|
||||
"The data {} has been failed.The saga has been crashed.",
|
||||
getName(), value);
|
||||
|
||||
return ChapterResult.failure(value);
|
||||
}
|
||||
|
||||
logger.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully",
|
||||
getName(), value);
|
||||
|
||||
return super.rollback(value);
|
||||
}
|
||||
}
|
||||
|
@ -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<String> {
|
||||
@Override
|
||||
public String getName() {
|
||||
|
@ -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<Chapter> 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 <K> Saga.Result kickOff(K value) {
|
||||
/**
|
||||
*
|
||||
* @param value incoming value
|
||||
* @param <K> type for incoming value
|
||||
* @return result @see {@link Saga.Result}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <K> Saga.Result execute(K value) {
|
||||
state.cleanUp();
|
||||
logger.info(" The new saga is about to start");
|
||||
Saga.Result result = FINISHED;
|
||||
K tempVal = value;
|
||||
|
||||
while (true) {
|
||||
int next = state.current();
|
||||
Saga.Chapter ch = saga.get(next);
|
||||
Optional<Chapter> srvOpt = sd.find(ch.name);
|
||||
|
||||
if (!srvOpt.isPresent()) {
|
||||
state.directionToBack();
|
||||
state.back();
|
||||
continue;
|
||||
}
|
||||
|
||||
Chapter srv = srvOpt.get();
|
||||
|
||||
if (state.isForward()) {
|
||||
ChapterResult processRes = srv.process(tempVal);
|
||||
if (processRes.isSuccess()) {
|
||||
next = state.forward();
|
||||
tempVal = (K) processRes.getValue();
|
||||
} else {
|
||||
state.directionToBack();
|
||||
}
|
||||
} else {
|
||||
ChapterResult rlRes = srv.rollback(tempVal);
|
||||
if (rlRes.isSuccess()) {
|
||||
next = state.back();
|
||||
tempVal = (K) rlRes.getValue();
|
||||
} else {
|
||||
result = CRASHED;
|
||||
next = state.back();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!saga.isPresent(next)) {
|
||||
return state.isForward() ? FINISHED : result == CRASHED ? CRASHED : ROLLBACK;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class CurrentState {
|
||||
int currentNumber;
|
||||
boolean isForward;
|
||||
|
||||
void cleanUp() {
|
||||
currentNumber = 0;
|
||||
isForward = true;
|
||||
}
|
||||
|
||||
CurrentState() {
|
||||
this.currentNumber = 0;
|
||||
this.isForward = true;
|
||||
}
|
||||
|
||||
|
||||
boolean isForward() {
|
||||
return isForward;
|
||||
}
|
||||
|
||||
void directionToBack() {
|
||||
isForward = false;
|
||||
}
|
||||
|
||||
int forward() {
|
||||
return ++currentNumber;
|
||||
}
|
||||
|
||||
int back() {
|
||||
return --currentNumber;
|
||||
}
|
||||
|
||||
int current() {
|
||||
return currentNumber;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 <K> type of incoming param
|
||||
*/
|
||||
public abstract class Service<K> implements Chapter<K> {
|
||||
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<K> implements Chapter<K> {
|
||||
|
||||
@Override
|
||||
public ChapterResult<K> 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<K> 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);
|
||||
}
|
||||
|
||||
|
@ -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<String, Chapter<?>> services;
|
||||
|
||||
public Optional<Chapter<?>> find(String service) {
|
||||
public Optional<Chapter> find(String service) {
|
||||
return Optional.ofNullable(services.getOrDefault(service, 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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<String> {
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -8,7 +33,10 @@ public class WithdrawMoneyService extends Service<String> {
|
||||
|
||||
@Override
|
||||
public ChapterResult<String> 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);
|
||||
|
@ -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[]{});
|
||||
}
|
||||
}
|
@ -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<String> 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<Integer> {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChapterResult<Integer> process(Integer value) {
|
||||
records.add("+1");
|
||||
return ChapterResult.success(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChapterResult<Integer> rollback(Integer value) {
|
||||
records.add("-1");
|
||||
return ChapterResult.success(value);
|
||||
}
|
||||
}
|
||||
|
||||
class Service2 extends Service<Integer> {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "2";
|
||||
}
|
||||
@Override
|
||||
public ChapterResult<Integer> process(Integer value) {
|
||||
records.add("+2");
|
||||
return ChapterResult.success(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChapterResult<Integer> rollback(Integer value) {
|
||||
records.add("-2");
|
||||
return ChapterResult.success(value);
|
||||
}
|
||||
}
|
||||
|
||||
class Service3 extends Service<Integer> {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "3";
|
||||
}
|
||||
@Override
|
||||
public ChapterResult<Integer> process(Integer value) {
|
||||
records.add("+3");
|
||||
return ChapterResult.success(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChapterResult<Integer> rollback(Integer value) {
|
||||
records.add("-3");
|
||||
return ChapterResult.success(value);
|
||||
}
|
||||
}
|
||||
|
||||
class Service4 extends Service<Integer> {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "4";
|
||||
}
|
||||
@Override
|
||||
public ChapterResult<Integer> process(Integer value) {
|
||||
records.add("+4");
|
||||
return ChapterResult.failure(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChapterResult<Integer> rollback(Integer value) {
|
||||
records.add("-4");
|
||||
return ChapterResult.success(value);
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user