change according to cgeckstyle

This commit is contained in:
Besok 2019-11-10 13:09:41 +00:00
parent 74c7273381
commit de56cbb971
27 changed files with 817 additions and 706 deletions

View File

@ -1,7 +1,7 @@
---
layout: pattern
title: Saga
folder: Communication
folder: saga
permalink: /patterns/saga/
categories: Behavioral
tags:
@ -43,4 +43,4 @@ Use the Saga pattern, if:
- you can not use 2PC(two phase commit)
## Credits
- [pattern description](https://microservices.io/patterns/data/saga.html)
- [Pattern: Saga](https://microservices.io/patterns/data/saga.html)

View File

@ -23,22 +23,23 @@
THE SOFTWARE.
-->
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.22.0-SNAPSHOT</version>
</parent>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.22.0-SNAPSHOT</version>
</parent>
<artifactId>saga</artifactId>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<artifactId>saga</artifactId>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -24,37 +24,40 @@ package com.iluwatar.saga.choreography;
/**
* Chapter is an interface representing a contract for an external service.
* ChoreographyChapter is an interface representing a contract for an external service.
* In that case, a service needs to make a decision what to do further
* hence the server needs to get all context representing {@link Saga}
* */
public interface Chapter {
*/
public interface ChoreographyChapter {
/**
* In that case, every method is responsible to make a decision on what to do then
* @param saga incoming saga
* @return saga result
*/
Saga execute(Saga saga);
/**
* In that case, every method is responsible to make a decision on what to do then
*
* @param saga incoming saga
* @return saga result
*/
Saga execute(Saga saga);
/**
* @return service name.
*/
String getName();
/**
* @return service name.
*/
String getName();
/**
* The operation executed in general case.
* @param saga incoming saga
* @return result {@link Saga}
*/
Saga process(Saga saga);
/**
* The operation executed in general case.
*
* @param saga incoming saga
* @return result {@link Saga}
*/
Saga process(Saga saga);
/**
* The operation executed in rollback case.
* @param saga incoming saga
* @return result {@link Saga}
*/
Saga rollback(Saga saga);
/**
* The operation executed in rollback case.
*
* @param saga incoming saga
* @return result {@link Saga}
*/
Saga rollback(Saga saga);
}

View File

@ -27,12 +27,12 @@ package com.iluwatar.saga.choreography;
* Class representing a service to book a fly
*/
public class FlyBookingService extends Service {
public FlyBookingService(ServiceDiscoveryService service) {
super(service);
}
public FlyBookingService(ServiceDiscoveryService service) {
super(service);
}
@Override
public String getName() {
return "booking a Fly";
}
@Override
public String getName() {
return "booking a Fly";
}
}

View File

@ -27,14 +27,14 @@ package com.iluwatar.saga.choreography;
* Class representing a service to book a hotel
*/
public class HotelBookingService extends Service {
public HotelBookingService(ServiceDiscoveryService service) {
super(service);
}
public HotelBookingService(ServiceDiscoveryService service) {
super(service);
}
@Override
public String getName() {
return "booking a Hotel";
}
@Override
public String getName() {
return "booking a Hotel";
}
}
}

View File

@ -26,14 +26,14 @@ package com.iluwatar.saga.choreography;
/**
* Class representing a service to init a new order.
*/
public class OrderService extends Service{
public class OrderService extends Service {
public OrderService(ServiceDiscoveryService service) {
super(service);
}
public OrderService(ServiceDiscoveryService service) {
super(service);
}
@Override
public String getName() {
return "init an order";
}
@Override
public String getName() {
return "init an order";
}
}

View File

@ -29,131 +29,187 @@ import java.util.List;
/**
* Saga representation.
* Saga consists of chapters.
* Every Chapter is executed a certain service.
* Every ChoreographyChapter is executed a certain service.
*/
public class Saga {
private List<Chapter> chapters;
private int pos;
private boolean forward;
private boolean finished;
private List<Chapter> chapters;
private int pos;
private boolean forward;
private boolean finished;
public static Saga create() {
return new Saga();
}
public SagaResult getResult() {
return !finished ?
SagaResult.PROGRESS :
forward ?
SagaResult.FINISHED :
SagaResult.ROLLBACKED;
}
public Saga chapter(String name) {
this.chapters.add(new Chapter(name));
return this;
}
public Saga setInValue(Object value){
if(chapters.isEmpty()){
return this;
}
chapters.get(chapters.size()-1).setInValue(value);
return this;
}
public Object getCurrentValue(){
return chapters.get(pos).getInValue();
}
public void setCurrentValue(Object value){
chapters.get(pos).setInValue(value);
}
public void setCurrentStatus(ChapterResult result){
chapters.get(pos).setResult(result);
public static Saga create() {
return new Saga();
}
/**
* get resuzlt of saga
*
* @return result of saga @see {@link SagaResult}
*/
public SagaResult getResult() {
if (finished) {
return forward
? SagaResult.FINISHED
: SagaResult.ROLLBACKED;
}
void setFinished(boolean finished) {
this.finished = finished;
}
boolean isForward() {
return forward;
}
int forward() {
return ++pos;
}
return SagaResult.PROGRESS;
}
int back() {
this.forward = false;
return --pos;
/**
* add chapter to saga
* @param name chapter name
* @return this
*/
public Saga chapter(String name) {
this.chapters.add(new Chapter(name));
return this;
}
/**
* set value to last chapter
* @param value invalue
* @return this
*/
public Saga setInValue(Object value) {
if (chapters.isEmpty()) {
return this;
}
chapters.get(chapters.size() - 1).setInValue(value);
return this;
}
/**
* get value from current chapter
* @return value
*/
public Object getCurrentValue() {
return chapters.get(pos).getInValue();
}
/**
* set value to current chapter
* @param value to set
*/
public void setCurrentValue(Object value) {
chapters.get(pos).setInValue(value);
}
/**
* set status for current chapter
* @param result to set
*/
public void setCurrentStatus(ChapterResult result) {
chapters.get(pos).setResult(result);
}
void setFinished(boolean finished) {
this.finished = finished;
}
boolean isForward() {
return forward;
}
int forward() {
return ++pos;
}
int back() {
this.forward = false;
return --pos;
}
public Saga() {
this.chapters = new ArrayList<>();
this.pos = 0;
this.forward = true;
this.finished = false;
}
private Saga() {
this.chapters = new ArrayList<>();
this.pos = 0;
this.forward = true;
this.finished = false;
}
Chapter getCurrent() {
return chapters.get(pos);
}
Chapter getCurrent() {
return chapters.get(pos);
}
boolean isPresent() {
return pos >= 0 && pos < chapters.size();
}
boolean isCurrentSuccess(){
return chapters.get(pos).isSuccess();
boolean isPresent() {
return pos >= 0 && pos < chapters.size();
}
boolean isCurrentSuccess() {
return chapters.get(pos).isSuccess();
}
/***
* Class presents a chapter status and incoming parameters(incoming parameter transforms to outcoming parameter)
*/
public static class Chapter {
private String name;
private ChapterResult result;
private Object inValue;
public Chapter(String name) {
this.name = name;
this.result = ChapterResult.INIT;
}
/***
* Class presents a chapter status and incoming parameters(incoming parameter transforms to outcoming parameter)
public Object getInValue() {
return inValue;
}
public void setInValue(Object object) {
this.inValue = object;
}
public String getName() {
return name;
}
/**
* set result
* @param result {@link ChapterResult}
*/
public static class Chapter {
private String name;
private ChapterResult result;
private Object inValue;
public Chapter(String name) {
this.name = name;
this.result = ChapterResult.INIT;
}
public Object getInValue() {
return inValue;
}
public void setInValue(Object object) {
this.inValue = object;
}
public String getName() {
return name;
}
public void setResult(ChapterResult result){
this.result = result;
}
public boolean isSuccess(){
return result == ChapterResult.SUCCESS;
}
public void setResult(ChapterResult result) {
this.result = result;
}
public enum ChapterResult {
INIT, SUCCESS, ROLLBACK
/**
* the result for chapter is good
* @return true if is good otherwise bad
*/
public boolean isSuccess() {
return result == ChapterResult.SUCCESS;
}
}
public enum SagaResult {
PROGRESS, FINISHED, ROLLBACKED
}
@Override
public String toString() {
return "Saga{" +
"chapters=" + Arrays.toString(chapters.toArray()) +
", pos=" + pos +
", forward=" + forward +
'}';
}
/**
* result for chapter
*/
public enum ChapterResult {
INIT, SUCCESS, ROLLBACK
}
/**
* result for saga
*/
public enum SagaResult {
PROGRESS, FINISHED, ROLLBACKED
}
@Override
public String toString() {
return "Saga{"
+ "chapters="
+ Arrays.toString(chapters.toArray())
+ ", pos="
+ pos
+ ", forward="
+ forward
+ '}';
}
}

View File

@ -44,34 +44,37 @@ import org.slf4j.LoggerFactory;
* @see Service
*/
public class SagaApplication {
private static final Logger logger = LoggerFactory.getLogger(SagaApplication.class);
private static final Logger LOGGER = LoggerFactory.getLogger(SagaApplication.class);
public static void main(String[] args) {
ServiceDiscoveryService sd = serviceDiscovery();
Chapter service = sd.findAny();
Saga goodOrderSaga = service.execute(newSaga("good_order"));
Saga badOrderSaga = service.execute(newSaga("bad_order"));
logger.info("orders: goodOrder is {}, badOrder is {}",
goodOrderSaga.getResult(), badOrderSaga.getResult());
/**
* main method
*/
public static void main(String[] args) {
ServiceDiscoveryService sd = serviceDiscovery();
ChoreographyChapter service = sd.findAny();
Saga goodOrderSaga = service.execute(newSaga("good_order"));
Saga badOrderSaga = service.execute(newSaga("bad_order"));
LOGGER.info("orders: goodOrder is {}, badOrder is {}",
goodOrderSaga.getResult(), badOrderSaga.getResult());
}
}
private static Saga newSaga(Object value) {
return Saga
.create()
.chapter("init an order").setInValue(value)
.chapter("booking a Fly")
.chapter("booking a Hotel")
.chapter("withdrawing Money");
}
private static Saga newSaga(Object value) {
return Saga
.create()
.chapter("init an order").setInValue(value)
.chapter("booking a Fly")
.chapter("booking a Hotel")
.chapter("withdrawing Money");
}
private static ServiceDiscoveryService serviceDiscovery() {
ServiceDiscoveryService sd = new ServiceDiscoveryService();
return sd
.discover(new OrderService(sd))
.discover(new FlyBookingService(sd))
.discover(new HotelBookingService(sd))
.discover(new WithdrawMoneyService(sd));
}
private static ServiceDiscoveryService serviceDiscovery() {
ServiceDiscoveryService sd = new ServiceDiscoveryService();
return sd
.discover(new OrderService(sd))
.discover(new FlyBookingService(sd))
.discover(new HotelBookingService(sd))
.discover(new WithdrawMoneyService(sd));
}
}

View File

@ -25,79 +25,85 @@ package com.iluwatar.saga.choreography;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.Supplier;
/**
* Common abstraction class representing services
* implementing a general contract @see {@link Chapter}
* implementing a general contract @see {@link ChoreographyChapter}
*/
public abstract class Service implements Chapter {
protected static final Logger logger = LoggerFactory.getLogger(Service.class);
public abstract class Service implements ChoreographyChapter {
protected static final Logger LOGGER = LoggerFactory.getLogger(Service.class);
private final ServiceDiscoveryService sd;
private final ServiceDiscoveryService sd;
public Service(ServiceDiscoveryService service) {
this.sd = service;
}
public Service(ServiceDiscoveryService service) {
this.sd = service;
}
@Override
public Saga execute(Saga saga) {
Saga nextSaga = saga;
Object nextVal;
String chapterName = saga.getCurrent().getName();
if (chapterName.equals(getName())) {
if (saga.isForward()) {
nextSaga = process(saga);
nextVal = nextSaga.getCurrentValue();
if (nextSaga.isCurrentSuccess()) {
nextSaga.forward();
} else {
nextSaga.back();
}
} else {
nextSaga = rollback(saga);
nextVal = nextSaga.getCurrentValue();
nextSaga.back();
}
if (isSagaFinished(nextSaga)) {
return nextSaga;
}
nextSaga.setCurrentValue(nextVal);
@Override
public Saga execute(Saga saga) {
Saga nextSaga = saga;
Object nextVal;
String chapterName = saga.getCurrent().getName();
if (chapterName.equals(getName())) {
if (saga.isForward()) {
nextSaga = process(saga);
nextVal = nextSaga.getCurrentValue();
if (nextSaga.isCurrentSuccess()) {
nextSaga.forward();
} else {
nextSaga.back();
}
Saga finalNextSaga = nextSaga;
} else {
nextSaga = rollback(saga);
nextVal = nextSaga.getCurrentValue();
nextSaga.back();
}
return sd.find(chapterName).map(ch -> ch.execute(finalNextSaga))
.orElseThrow(RuntimeException::new);
if (isSagaFinished(nextSaga)) {
return nextSaga;
}
nextSaga.setCurrentValue(nextVal);
}
Saga finalNextSaga = nextSaga;
@Override
public Saga process(Saga saga) {
Object inValue = saga.getCurrentValue();
logger.info("The chapter '{}' has been started. The data {} has been stored or calculated successfully",
getName(), inValue);
saga.setCurrentStatus(Saga.ChapterResult.SUCCESS);
saga.setCurrentValue(inValue);
return saga;
}
@Override
public Saga rollback(Saga saga) {
Object inValue = saga.getCurrentValue();
logger.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully",
getName(), inValue);
saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK);
saga.setCurrentValue(inValue);
return saga;
}
private boolean isSagaFinished(Saga saga) {
if (!saga.isPresent()) {
saga.setFinished(true);
logger.info(" the saga has been finished with {} status", saga.getResult());
return true;
}
return false;
return sd.find(chapterName).map(ch -> ch.execute(finalNextSaga))
.orElseThrow(serviceNotFoundException(chapterName));
}
private Supplier<RuntimeException> serviceNotFoundException(String chServiceName) {
return () -> new RuntimeException(String.format("the service %s has not been found", chServiceName));
}
@Override
public Saga process(Saga saga) {
Object inValue = saga.getCurrentValue();
LOGGER.info("The chapter '{}' has been started. The data {} has been stored or calculated successfully",
getName(), inValue);
saga.setCurrentStatus(Saga.ChapterResult.SUCCESS);
saga.setCurrentValue(inValue);
return saga;
}
@Override
public Saga rollback(Saga saga) {
Object inValue = saga.getCurrentValue();
LOGGER.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully",
getName(), inValue);
saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK);
saga.setCurrentValue(inValue);
return saga;
}
private boolean isSagaFinished(Saga saga) {
if (!saga.isPresent()) {
saga.setFinished(true);
LOGGER.info(" the saga has been finished with {} status", saga.getResult());
return true;
}
return false;
}
}

View File

@ -32,29 +32,30 @@ import java.util.Optional;
* The class representing a service discovery pattern.
*/
public class ServiceDiscoveryService {
private Map<String, Chapter> services;
private Map<String, ChoreographyChapter> services;
/**
* find any service
* @return any service
* @throws NoSuchElementException if no elements further
*/
public Chapter findAny(){
return services.values().iterator().next();
}
/**
* find any service
*
* @return any service
* @throws NoSuchElementException if no elements further
*/
public ChoreographyChapter findAny() {
return services.values().iterator().next();
}
public Optional<Chapter> find(String service) {
return Optional.ofNullable(services.getOrDefault(service, null));
}
public Optional<ChoreographyChapter> find(String service) {
return Optional.ofNullable(services.getOrDefault(service, null));
}
public ServiceDiscoveryService discover(Chapter chapterService) {
services.put(chapterService.getName(), chapterService);
return this;
}
public ServiceDiscoveryService discover(ChoreographyChapter chapterService) {
services.put(chapterService.getName(), chapterService);
return this;
}
public ServiceDiscoveryService() {
this.services = new HashMap<>();
}
public ServiceDiscoveryService() {
this.services = new HashMap<>();
}
}

View File

@ -28,26 +28,26 @@ package com.iluwatar.saga.choreography;
*/
public class WithdrawMoneyService extends Service {
public WithdrawMoneyService(ServiceDiscoveryService service) {
super(service);
}
public WithdrawMoneyService(ServiceDiscoveryService service) {
super(service);
}
@Override
public String getName() {
return "withdrawing Money";
}
@Override
public String getName() {
return "withdrawing Money";
}
@Override
public Saga process(Saga saga) {
Object inValue = saga.getCurrentValue();
@Override
public Saga process(Saga saga) {
Object inValue = saga.getCurrentValue();
if (inValue.equals("bad_order") ) {
logger.info("The chapter '{}' has been started. But the exception has been raised." +
"The rollback is about to start",
getName(), inValue);
saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK);
return saga;
}
return super.process(saga);
if (inValue.equals("bad_order")) {
LOGGER.info("The chapter '{}' has been started. But the exception has been raised."
+ "The rollback is about to start",
getName(), inValue);
saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK);
return saga;
}
return super.process(saga);
}
}

View File

@ -24,34 +24,38 @@ package com.iluwatar.saga.orchestration;
/**
* Executing result for chapter
*
* @param <K> incoming value
*/
public class ChapterResult<K> {
private K value;
private State state;
private K value;
private State state;
public K getValue() {
return value;
}
public K getValue() {
return value;
}
ChapterResult(K value, State state) {
this.value = value;
this.state = state;
}
ChapterResult(K value, State state) {
this.value = value;
this.state = state;
}
public boolean isSuccess(){
return state == State.SUCCESS;
}
public boolean isSuccess() {
return state == State.SUCCESS;
}
public static <K> ChapterResult<K> success(K val) {
return new ChapterResult<>(val, State.SUCCESS);
}
public static <K> ChapterResult<K> success(K val) {
return new ChapterResult<>(val, State.SUCCESS);
}
public static <K> ChapterResult<K> failure(K val) {
return new ChapterResult<>(val, State.FAILURE);
}
public static <K> ChapterResult<K> failure(K val) {
return new ChapterResult<>(val, State.FAILURE);
}
public enum State {
SUCCESS, FAILURE
}
/**
* state for chapter
*/
public enum State {
SUCCESS, FAILURE
}
}

View File

@ -26,8 +26,8 @@ package com.iluwatar.saga.orchestration;
* Class representing a service to book a fly
*/
public class FlyBookingService extends Service<String> {
@Override
public String getName() {
return "booking a Fly";
}
@Override
public String getName() {
return "booking a Fly";
}
}

View File

@ -26,25 +26,25 @@ package com.iluwatar.saga.orchestration;
* Class representing a service to book a hotel
*/
public class HotelBookingService extends Service<String> {
@Override
public String getName() {
return "booking a Hotel";
@Override
public String getName() {
return "booking a Hotel";
}
@Override
public ChapterResult<String> rollback(String value) {
if (value.equals("crashed_order")) {
LOGGER.info("The Rollback for a chapter '{}' has been started. "
+ "The data {} has been failed.The saga has been crashed.",
getName(), value);
return ChapterResult.failure(value);
}
LOGGER.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully",
getName(), value);
@Override
public ChapterResult<String> rollback(String value) {
if(value.equals("crashed_order")){
logger.info("The Rollback for a chapter '{}' has been started. " +
"The data {} has been failed.The saga has been crashed.",
getName(), value);
return ChapterResult.failure(value);
}
logger.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully",
getName(), value);
return super.rollback(value);
}
return super.rollback(value);
}
}

View File

@ -23,28 +23,31 @@
package com.iluwatar.saga.orchestration;
/**
* Chapter is an interface representing a contract for an external service.
* ChoreographyChapter is an interface representing a contract for an external service.
*
* @param <K> is type for passing params
*/
public interface Chapter<K> {
public interface OrchestrationChapter<K> {
/**
* @return service name.
*/
String getName();
/**
* @return service name.
*/
String getName();
/**
* The operation executed in general case.
* @param value incoming value
* @return result {@link ChapterResult}
*/
ChapterResult<K> process(K value);
/**
* The operation executed in general case.
*
* @param value incoming value
* @return result {@link ChapterResult}
*/
ChapterResult<K> process(K value);
/**
* The operation executed in rollback case.
* @param value incoming value
* @return result {@link ChapterResult}
*/
ChapterResult<K> rollback(K value);
/**
* The operation executed in rollback case.
*
* @param value incoming value
* @return result {@link ChapterResult}
*/
ChapterResult<K> rollback(K value);
}

View File

@ -26,8 +26,8 @@ package com.iluwatar.saga.orchestration;
* Class representing a service to init a new order.
*/
public class OrderService extends Service<String> {
@Override
public String getName() {
return "init an order";
}
@Override
public String getName() {
return "init an order";
}
}

View File

@ -28,49 +28,56 @@ import java.util.List;
/**
* Saga representation.
* Saga consists of chapters.
* Every Chapter is executed by a certain service.
* Every ChoreographyChapter is executed by a certain service.
*/
public class Saga {
private List<Chapter> chapters;
private List<Chapter> chapters;
public Saga() {
this.chapters = new ArrayList<>();
private Saga() {
this.chapters = new ArrayList<>();
}
public Saga chapter(String name) {
this.chapters.add(new Chapter(name));
return this;
}
public Chapter get(int idx) {
return chapters.get(idx);
}
public boolean isPresent(int idx) {
return idx >= 0 && idx < chapters.size();
}
public static Saga create() {
return new Saga();
}
/**
* result for saga
*/
public enum Result {
FINISHED, ROLLBACK, CRASHED
}
/**
* class represents chapter name
*/
public static class Chapter {
String name;
public Chapter(String name) {
this.name = name;
}
public Saga chapter(String name) {
this.chapters.add(new Chapter(name));
return this;
}
public Chapter get(int idx) {
return chapters.get(idx);
}
public boolean isPresent(int idx) {
return idx >= 0 && idx < chapters.size();
}
public static Saga create() {
return new Saga();
}
public enum Result {
FINISHED, ROLLBACK, CRASHED
}
public static class Chapter {
String name;
public Chapter(String name) {
this.name = name;
}
public String getName() {
return name;
}
public String getName() {
return name;
}
}
}

View File

@ -31,11 +31,12 @@ import org.slf4j.LoggerFactory;
* This pattern is used in distributed services to perform a group of operations atomically.
* This is an analog of transaction in a database but in terms of microservices architecture this is executed
* in a distributed environment
*
* <p>
* A saga is a sequence of local transactions in a certain context. If one transaction fails for some reason,
* the saga executes compensating transactions(rollbacks) to undo the impact of the preceding transactions.
*
* In this approach, there is an orchestrator @see {@link SagaOrchestrator} that manages all the transactions and directs
* <p>
* In this approach, there is an orchestrator @see {@link SagaOrchestrator}
* that manages all the transactions and directs
* the participant services to execute local transactions based on events.
* The major difference with choreography saga is an ability to handle crashed services
* (otherwise in choreography services very hard to prevent a saga if one of them has been crashed)
@ -45,34 +46,37 @@ import org.slf4j.LoggerFactory;
* @see Service
*/
public class SagaApplication {
private static final Logger logger = LoggerFactory.getLogger(SagaApplication.class);
private static final Logger LOGGER = LoggerFactory.getLogger(SagaApplication.class);
public static void main(String[] args) {
SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery());
/**
* method to show common saga logic
*/
public static void main(String[] args) {
SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery());
Saga.Result goodOrder = sagaOrchestrator.execute("good_order");
Saga.Result badOrder = sagaOrchestrator.execute("bad_order");
Saga.Result crashedOrder = sagaOrchestrator.execute("crashed_order");
Saga.Result goodOrder = sagaOrchestrator.execute("good_order");
Saga.Result badOrder = sagaOrchestrator.execute("bad_order");
Saga.Result crashedOrder = sagaOrchestrator.execute("crashed_order");
logger.info("orders: goodOrder is {}, badOrder is {},crashedOrder is {}",goodOrder,badOrder,crashedOrder);
}
LOGGER.info("orders: goodOrder is {}, badOrder is {},crashedOrder is {}", goodOrder, badOrder, crashedOrder);
}
private static Saga newSaga() {
return Saga
.create()
.chapter("init an order")
.chapter("booking a Fly")
.chapter("booking a Hotel")
.chapter("withdrawing Money");
}
private static Saga newSaga() {
return Saga
.create()
.chapter("init an order")
.chapter("booking a Fly")
.chapter("booking a Hotel")
.chapter("withdrawing Money");
}
private static ServiceDiscoveryService serviceDiscovery() {
return
new ServiceDiscoveryService()
.discover(new OrderService())
.discover(new FlyBookingService())
.discover(new HotelBookingService())
.discover(new WithdrawMoneyService());
}
private static ServiceDiscoveryService serviceDiscovery() {
return
new ServiceDiscoveryService()
.discover(new OrderService())
.discover(new FlyBookingService())
.discover(new HotelBookingService())
.discover(new WithdrawMoneyService());
}
}

View File

@ -34,107 +34,112 @@ import static com.iluwatar.saga.orchestration.Saga.Result.*;
* the participant services to execute local transactions based on events.
*/
public class SagaOrchestrator {
private static final Logger logger = LoggerFactory.getLogger(SagaOrchestrator.class);
private final Saga saga;
private final ServiceDiscoveryService sd;
private final CurrentState state;
private static final Logger LOGGER = LoggerFactory.getLogger(SagaOrchestrator.class);
private final Saga saga;
private final ServiceDiscoveryService sd;
private final CurrentState state;
public SagaOrchestrator(Saga saga, ServiceDiscoveryService sd) {
this.saga = saga;
this.sd = sd;
this.state = new CurrentState();
/**
* Create a new service to orchetrate sagas
* @param saga saga to process
* @param sd service discovery @see {@link ServiceDiscoveryService}
*/
public SagaOrchestrator(Saga saga, ServiceDiscoveryService sd) {
this.saga = saga;
this.sd = sd;
this.state = new CurrentState();
}
/**
* pipeline to execute saga process/story
*
* @param value incoming value
* @param <K> type for incoming value
* @return result @see {@link Saga.Result}
*/
@SuppressWarnings("unchecked")
public <K> Saga.Result execute(K value) {
state.cleanUp();
LOGGER.info(" The new saga is about to start");
Saga.Result result = FINISHED;
K tempVal = value;
while (true) {
int next = state.current();
Saga.Chapter ch = saga.get(next);
Optional<OrchestrationChapter> srvOpt = sd.find(ch.name);
if (!srvOpt.isPresent()) {
state.directionToBack();
state.back();
continue;
}
OrchestrationChapter srv = srvOpt.get();
if (state.isForward()) {
ChapterResult processRes = srv.process(tempVal);
if (processRes.isSuccess()) {
next = state.forward();
tempVal = (K) processRes.getValue();
} else {
state.directionToBack();
}
} else {
ChapterResult rlRes = srv.rollback(tempVal);
if (rlRes.isSuccess()) {
next = state.back();
tempVal = (K) rlRes.getValue();
} else {
result = CRASHED;
next = state.back();
}
}
if (!saga.isPresent(next)) {
return state.isForward() ? FINISHED : result == CRASHED ? CRASHED : ROLLBACK;
}
}
/**
* pipeline to execute saga process/story
*
* @param value incoming value
* @param <K> type for incoming value
* @return result @see {@link Saga.Result}
*/
@SuppressWarnings("unchecked")
public <K> Saga.Result execute(K value) {
state.cleanUp();
logger.info(" The new saga is about to start");
Saga.Result result = FINISHED;
K tempVal = value;
while (true) {
int next = state.current();
Saga.Chapter ch = saga.get(next);
Optional<Chapter> srvOpt = sd.find(ch.name);
if (!srvOpt.isPresent()) {
state.directionToBack();
state.back();
continue;
}
Chapter srv = srvOpt.get();
if (state.isForward()) {
ChapterResult processRes = srv.process(tempVal);
if (processRes.isSuccess()) {
next = state.forward();
tempVal = (K) processRes.getValue();
} else {
state.directionToBack();
}
} else {
ChapterResult rlRes = srv.rollback(tempVal);
if (rlRes.isSuccess()) {
next = state.back();
tempVal = (K) rlRes.getValue();
} else {
result = CRASHED;
next = state.back();
}
}
}
if (!saga.isPresent(next)) {
return state.isForward() ? FINISHED : result == CRASHED ? CRASHED : ROLLBACK;
}
}
private static class CurrentState {
int currentNumber;
boolean isForward;
void cleanUp() {
currentNumber = 0;
isForward = true;
}
CurrentState() {
this.currentNumber = 0;
this.isForward = true;
}
private static class CurrentState {
int currentNumber;
boolean isForward;
void cleanUp() {
currentNumber = 0;
isForward = true;
}
CurrentState() {
this.currentNumber = 0;
this.isForward = true;
}
boolean isForward() {
return isForward;
}
void directionToBack() {
isForward = false;
}
int forward() {
return ++currentNumber;
}
int back() {
return --currentNumber;
}
int current() {
return currentNumber;
}
boolean isForward() {
return isForward;
}
void directionToBack() {
isForward = false;
}
int forward() {
return ++currentNumber;
}
int back() {
return --currentNumber;
}
int current() {
return currentNumber;
}
}
}

View File

@ -27,29 +27,30 @@ import org.slf4j.LoggerFactory;
/**
* Common abstraction class representing services
* implementing a general contract @see {@link Chapter}
* implementing a general contract @see {@link OrchestrationChapter}
*
* @param <K> type of incoming param
*/
public abstract class Service<K> implements Chapter<K> {
protected static final Logger logger = LoggerFactory.getLogger(Service.class);
public abstract class Service<K> implements OrchestrationChapter<K> {
protected static final Logger LOGGER = LoggerFactory.getLogger(Service.class);
@Override
public abstract String getName();
@Override
public abstract String getName();
@Override
public ChapterResult<K> process(K value) {
logger.info("The chapter '{}' has been started. The data {} has been stored or calculated successfully",
getName(),value);
return ChapterResult.success(value);
}
@Override
public ChapterResult<K> process(K value) {
LOGGER.info("The chapter '{}' has been started. The data {} has been stored or calculated successfully",
getName(), value);
return ChapterResult.success(value);
}
@Override
public ChapterResult<K> rollback(K value) {
logger.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully",
getName(),value);
return ChapterResult.success(value);
}
@Override
public ChapterResult<K> rollback(K value) {
LOGGER.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully",
getName(), value);
return ChapterResult.success(value);
}
}

View File

@ -30,20 +30,20 @@ import java.util.Optional;
* The class representing a service discovery pattern.
*/
public class ServiceDiscoveryService {
private Map<String, Chapter<?>> services;
private Map<String, OrchestrationChapter<?>> services;
public Optional<Chapter> find(String service) {
return Optional.ofNullable(services.getOrDefault(service, null));
}
public Optional<OrchestrationChapter> find(String service) {
return Optional.ofNullable(services.getOrDefault(service, null));
}
public ServiceDiscoveryService discover(Chapter<?> chapterService) {
services.put(chapterService.getName(), chapterService);
return this;
}
public ServiceDiscoveryService discover(OrchestrationChapter<?> orchestrationChapterService) {
services.put(orchestrationChapterService.getName(), orchestrationChapterService);
return this;
}
public ServiceDiscoveryService() {
this.services = new HashMap<>();
}
public ServiceDiscoveryService() {
this.services = new HashMap<>();
}
}

View File

@ -26,19 +26,19 @@ package com.iluwatar.saga.orchestration;
* Class representing a service to withdraw a money
*/
public class WithdrawMoneyService extends Service<String> {
@Override
public String getName() {
return "withdrawing Money";
}
@Override
public String getName() {
return "withdrawing Money";
}
@Override
public ChapterResult<String> process(String value) {
if (value.equals("bad_order") || value.equals("crashed_order")) {
logger.info("The chapter '{}' has been started. But the exception has been raised." +
"The rollback is about to start",
getName(), value);
return ChapterResult.failure(value);
}
return super.process(value);
@Override
public ChapterResult<String> process(String value) {
if (value.equals("bad_order") || value.equals("crashed_order")) {
LOGGER.info("The chapter '{}' has been started. But the exception has been raised."
+ "The rollback is about to start",
getName(), value);
return ChapterResult.failure(value);
}
return super.process(value);
}
}

View File

@ -25,11 +25,13 @@ package com.iluwatar.saga.choreography;
import com.iluwatar.saga.orchestration.SagaApplication;
import org.junit.Test;
import static org.junit.Assert.*;
/***
* empty test
*/
public class SagaApplicationTest {
@Test
public void mainTest() {
SagaApplication.main(new String[]{});
}
@Test
public void mainTest() {
SagaApplication.main(new String[]{});
}
}

View File

@ -25,35 +25,38 @@ package com.iluwatar.saga.choreography;
import org.junit.Assert;
import org.junit.Test;
/**
* test to check choreography saga
*/
public class SagaChoreographyTest {
@Test
public void executeTest() {
ServiceDiscoveryService sd = serviceDiscovery();
Chapter service = sd.findAny();
Saga badOrderSaga = service.execute(newSaga("bad_order"));
Saga goodOrderSaga = service.execute(newSaga("good_order"));
@Test
public void executeTest() {
ServiceDiscoveryService sd = serviceDiscovery();
ChoreographyChapter service = sd.findAny();
Saga badOrderSaga = service.execute(newSaga("bad_order"));
Saga goodOrderSaga = service.execute(newSaga("good_order"));
Assert.assertEquals(badOrderSaga.getResult(), Saga.SagaResult.ROLLBACKED);
Assert.assertEquals(goodOrderSaga.getResult(), Saga.SagaResult.FINISHED);
}
Assert.assertEquals(badOrderSaga.getResult(), Saga.SagaResult.ROLLBACKED);
Assert.assertEquals(goodOrderSaga.getResult(), Saga.SagaResult.FINISHED);
}
private static Saga newSaga(Object value) {
return Saga
.create()
.chapter("init an order").setInValue(value)
.chapter("booking a Fly")
.chapter("booking a Hotel")
.chapter("withdrawing Money");
}
private static Saga newSaga(Object value) {
return Saga
.create()
.chapter("init an order").setInValue(value)
.chapter("booking a Fly")
.chapter("booking a Hotel")
.chapter("withdrawing Money");
}
private static ServiceDiscoveryService serviceDiscovery() {
ServiceDiscoveryService sd = new ServiceDiscoveryService();
return sd
.discover(new OrderService(sd))
.discover(new FlyBookingService(sd))
.discover(new HotelBookingService(sd))
.discover(new WithdrawMoneyService(sd));
}
private static ServiceDiscoveryService serviceDiscovery() {
ServiceDiscoveryService sd = new ServiceDiscoveryService();
return sd
.discover(new OrderService(sd))
.discover(new FlyBookingService(sd))
.discover(new HotelBookingService(sd))
.discover(new WithdrawMoneyService(sd));
}
}

View File

@ -26,10 +26,13 @@ import org.junit.Test;
import static org.junit.Assert.*;
/**
* empty test
*/
public class SagaApplicationTest {
@Test
public void mainTest() {
SagaApplication.main(new String[]{});
}
@Test
public void mainTest() {
SagaApplication.main(new String[]{});
}
}

View File

@ -28,112 +28,118 @@ import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
/**
* test to test orchestration logic
*/
public class SagaOrchestratorInternallyTest {
private List<String> records = new ArrayList<>();
private List<String> records = new ArrayList<>();
@Test
public void executeTest() {
SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery());
Saga.Result result = sagaOrchestrator.execute(1);
Assert.assertEquals(result, Saga.Result.ROLLBACK);
Assert.assertArrayEquals(
records.toArray(new String[]{}),
new String[]{"+1","+2","+3","+4","-4","-3","-2","-1"});
@Test
public void executeTest() {
SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery());
Saga.Result result = sagaOrchestrator.execute(1);
Assert.assertEquals(result, Saga.Result.ROLLBACK);
Assert.assertArrayEquals(
records.toArray(new String[]{}),
new String[]{"+1", "+2", "+3", "+4", "-4", "-3", "-2", "-1"});
}
private static Saga newSaga() {
return Saga
.create()
.chapter("1")
.chapter("2")
.chapter("3")
.chapter("4");
}
private ServiceDiscoveryService serviceDiscovery() {
return
new ServiceDiscoveryService()
.discover(new Service1())
.discover(new Service2())
.discover(new Service3())
.discover(new Service4());
}
class Service1 extends Service<Integer> {
@Override
public String getName() {
return "1";
}
private static Saga newSaga() {
return Saga
.create()
.chapter("1")
.chapter("2")
.chapter("3")
.chapter("4");
@Override
public ChapterResult<Integer> process(Integer value) {
records.add("+1");
return ChapterResult.success(value);
}
private ServiceDiscoveryService serviceDiscovery() {
return
new ServiceDiscoveryService()
.discover(new Service1())
.discover(new Service2())
.discover(new Service3())
.discover(new Service4());
@Override
public ChapterResult<Integer> rollback(Integer value) {
records.add("-1");
return ChapterResult.success(value);
}
}
class Service2 extends Service<Integer> {
@Override
public String getName() {
return "2";
}
class Service1 extends Service<Integer> {
@Override
public String getName() {
return "1";
}
@Override
public ChapterResult<Integer> process(Integer value) {
records.add("+1");
return ChapterResult.success(value);
}
@Override
public ChapterResult<Integer> rollback(Integer value) {
records.add("-1");
return ChapterResult.success(value);
}
@Override
public ChapterResult<Integer> process(Integer value) {
records.add("+2");
return ChapterResult.success(value);
}
class Service2 extends Service<Integer> {
@Override
public ChapterResult<Integer> rollback(Integer value) {
records.add("-2");
return ChapterResult.success(value);
}
}
@Override
public String getName() {
return "2";
}
@Override
public ChapterResult<Integer> process(Integer value) {
records.add("+2");
return ChapterResult.success(value);
}
class Service3 extends Service<Integer> {
@Override
public ChapterResult<Integer> rollback(Integer value) {
records.add("-2");
return ChapterResult.success(value);
}
@Override
public String getName() {
return "3";
}
class Service3 extends Service<Integer> {
@Override
public String getName() {
return "3";
}
@Override
public ChapterResult<Integer> process(Integer value) {
records.add("+3");
return ChapterResult.success(value);
}
@Override
public ChapterResult<Integer> rollback(Integer value) {
records.add("-3");
return ChapterResult.success(value);
}
@Override
public ChapterResult<Integer> process(Integer value) {
records.add("+3");
return ChapterResult.success(value);
}
class Service4 extends Service<Integer> {
@Override
public String getName() {
return "4";
}
@Override
public ChapterResult<Integer> process(Integer value) {
records.add("+4");
return ChapterResult.failure(value);
}
@Override
public ChapterResult<Integer> rollback(Integer value) {
records.add("-4");
return ChapterResult.success(value);
}
@Override
public ChapterResult<Integer> rollback(Integer value) {
records.add("-3");
return ChapterResult.success(value);
}
}
class Service4 extends Service<Integer> {
@Override
public String getName() {
return "4";
}
@Override
public ChapterResult<Integer> process(Integer value) {
records.add("+4");
return ChapterResult.failure(value);
}
@Override
public ChapterResult<Integer> rollback(Integer value) {
records.add("-4");
return ChapterResult.success(value);
}
}
}

View File

@ -25,33 +25,36 @@ package com.iluwatar.saga.orchestration;
import org.junit.Assert;
import org.junit.Test;
/**
* test to check general logic
*/
public class SagaOrchestratorTest {
@Test
public void execute() {
SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery());
Saga.Result badOrder = sagaOrchestrator.execute("bad_order");
Saga.Result crashedOrder = sagaOrchestrator.execute("crashed_order");
@Test
public void execute() {
SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery());
Saga.Result badOrder = sagaOrchestrator.execute("bad_order");
Saga.Result crashedOrder = sagaOrchestrator.execute("crashed_order");
Assert.assertEquals(badOrder, Saga.Result.ROLLBACK);
Assert.assertEquals(crashedOrder, Saga.Result.CRASHED);
}
Assert.assertEquals(badOrder, Saga.Result.ROLLBACK);
Assert.assertEquals(crashedOrder, Saga.Result.CRASHED);
}
private static Saga newSaga() {
return Saga
.create()
.chapter("init an order")
.chapter("booking a Fly")
.chapter("booking a Hotel")
.chapter("withdrawing Money");
}
private static Saga newSaga() {
return Saga
.create()
.chapter("init an order")
.chapter("booking a Fly")
.chapter("booking a Hotel")
.chapter("withdrawing Money");
}
private static ServiceDiscoveryService serviceDiscovery() {
return
new ServiceDiscoveryService()
.discover(new OrderService())
.discover(new FlyBookingService())
.discover(new HotelBookingService())
.discover(new WithdrawMoneyService());
}
private static ServiceDiscoveryService serviceDiscovery() {
return
new ServiceDiscoveryService()
.discover(new OrderService())
.discover(new FlyBookingService())
.discover(new HotelBookingService())
.discover(new WithdrawMoneyService());
}
}