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 layout: pattern
title: Saga title: Saga
folder: Communication folder: saga
permalink: /patterns/saga/ permalink: /patterns/saga/
categories: Behavioral categories: Behavioral
tags: tags:
@ -43,4 +43,4 @@ Use the Saga pattern, if:
- you can not use 2PC(two phase commit) - you can not use 2PC(two phase commit)
## Credits ## 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. 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" <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> xmlns="http://maven.apache.org/POM/4.0.0"
<modelVersion>4.0.0</modelVersion> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<parent> <modelVersion>4.0.0</modelVersion>
<groupId>com.iluwatar</groupId> <parent>
<artifactId>java-design-patterns</artifactId> <groupId>com.iluwatar</groupId>
<version>1.22.0-SNAPSHOT</version> <artifactId>java-design-patterns</artifactId>
</parent> <version>1.22.0-SNAPSHOT</version>
</parent>
<artifactId>saga</artifactId> <artifactId>saga</artifactId>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </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 * 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} * 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 * In that case, every method is responsible to make a decision on what to do then
* @param saga incoming saga *
* @return saga result * @param saga incoming saga
*/ * @return saga result
Saga execute(Saga saga); */
Saga execute(Saga saga);
/** /**
* @return service name. * @return service name.
*/ */
String getName(); String getName();
/** /**
* The operation executed in general case. * The operation executed in general case.
* @param saga incoming saga *
* @return result {@link Saga} * @param saga incoming saga
*/ * @return result {@link Saga}
Saga process(Saga saga); */
Saga process(Saga saga);
/** /**
* The operation executed in rollback case. * The operation executed in rollback case.
* @param saga incoming saga *
* @return result {@link Saga} * @param saga incoming saga
*/ * @return result {@link Saga}
Saga rollback(Saga saga); */
Saga rollback(Saga saga);
} }

View File

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

View File

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

View File

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

View File

@ -29,131 +29,187 @@ import java.util.List;
/** /**
* Saga representation. * Saga representation.
* Saga consists of chapters. * Saga consists of chapters.
* Every Chapter is executed a certain service. * Every ChoreographyChapter is executed a certain service.
*/ */
public class Saga { public class Saga {
private List<Chapter> chapters; private List<Chapter> chapters;
private int pos; private int pos;
private boolean forward; private boolean forward;
private boolean finished; private boolean finished;
public static Saga create() { public static Saga create() {
return new Saga(); return new Saga();
} }
public SagaResult getResult() {
return !finished ? /**
SagaResult.PROGRESS : * get resuzlt of saga
forward ? *
SagaResult.FINISHED : * @return result of saga @see {@link SagaResult}
SagaResult.ROLLBACKED; */
} public SagaResult getResult() {
public Saga chapter(String name) { if (finished) {
this.chapters.add(new Chapter(name)); return forward
return this; ? SagaResult.FINISHED
} : SagaResult.ROLLBACKED;
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);
} }
void setFinished(boolean finished) { return SagaResult.PROGRESS;
this.finished = finished; }
}
boolean isForward() {
return forward;
}
int forward() {
return ++pos;
}
int back() { /**
this.forward = false; * add chapter to saga
return --pos; * @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() { private Saga() {
this.chapters = new ArrayList<>(); this.chapters = new ArrayList<>();
this.pos = 0; this.pos = 0;
this.forward = true; this.forward = true;
this.finished = false; this.finished = false;
} }
Chapter getCurrent() { Chapter getCurrent() {
return chapters.get(pos); return chapters.get(pos);
} }
boolean isPresent() { boolean isPresent() {
return pos >= 0 && pos < chapters.size(); return pos >= 0 && pos < chapters.size();
} }
boolean isCurrentSuccess(){
return chapters.get(pos).isSuccess(); 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() {
* Class presents a chapter status and incoming parameters(incoming parameter transforms to outcoming parameter) 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 { public void setResult(ChapterResult result) {
private String name; this.result = result;
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 enum ChapterResult { * the result for chapter is good
INIT, SUCCESS, ROLLBACK * @return true if is good otherwise bad
*/
public boolean isSuccess() {
return result == ChapterResult.SUCCESS;
} }
}
public enum SagaResult {
PROGRESS, FINISHED, ROLLBACKED
}
@Override /**
public String toString() { * result for chapter
return "Saga{" + */
"chapters=" + Arrays.toString(chapters.toArray()) + public enum ChapterResult {
", pos=" + pos + INIT, SUCCESS, ROLLBACK
", forward=" + forward + }
'}';
} /**
* 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 * @see Service
*/ */
public class SagaApplication { 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(); * main method
Chapter service = sd.findAny(); */
Saga goodOrderSaga = service.execute(newSaga("good_order")); public static void main(String[] args) {
Saga badOrderSaga = service.execute(newSaga("bad_order")); ServiceDiscoveryService sd = serviceDiscovery();
logger.info("orders: goodOrder is {}, badOrder is {}", ChoreographyChapter service = sd.findAny();
goodOrderSaga.getResult(), badOrderSaga.getResult()); 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) { private static Saga newSaga(Object value) {
return Saga return Saga
.create() .create()
.chapter("init an order").setInValue(value) .chapter("init an order").setInValue(value)
.chapter("booking a Fly") .chapter("booking a Fly")
.chapter("booking a Hotel") .chapter("booking a Hotel")
.chapter("withdrawing Money"); .chapter("withdrawing Money");
} }
private static ServiceDiscoveryService serviceDiscovery() { private static ServiceDiscoveryService serviceDiscovery() {
ServiceDiscoveryService sd = new ServiceDiscoveryService(); ServiceDiscoveryService sd = new ServiceDiscoveryService();
return sd return sd
.discover(new OrderService(sd)) .discover(new OrderService(sd))
.discover(new FlyBookingService(sd)) .discover(new FlyBookingService(sd))
.discover(new HotelBookingService(sd)) .discover(new HotelBookingService(sd))
.discover(new WithdrawMoneyService(sd)); .discover(new WithdrawMoneyService(sd));
} }
} }

View File

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

View File

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

View File

@ -24,34 +24,38 @@ package com.iluwatar.saga.orchestration;
/** /**
* Executing result for chapter * Executing result for chapter
*
* @param <K> incoming value * @param <K> incoming value
*/ */
public class ChapterResult<K> { public class ChapterResult<K> {
private K value; private K value;
private State state; private State state;
public K getValue() { public K getValue() {
return value; return value;
} }
ChapterResult(K value, State state) { ChapterResult(K value, State state) {
this.value = value; this.value = value;
this.state = state; this.state = state;
} }
public boolean isSuccess(){ public boolean isSuccess() {
return state == State.SUCCESS; return state == State.SUCCESS;
} }
public static <K> ChapterResult<K> success(K val) { public static <K> ChapterResult<K> success(K val) {
return new ChapterResult<>(val, State.SUCCESS); return new ChapterResult<>(val, State.SUCCESS);
} }
public static <K> ChapterResult<K> failure(K val) { public static <K> ChapterResult<K> failure(K val) {
return new ChapterResult<>(val, State.FAILURE); 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 * Class representing a service to book a fly
*/ */
public class FlyBookingService extends Service<String> { public class FlyBookingService extends Service<String> {
@Override @Override
public String getName() { public String getName() {
return "booking a Fly"; return "booking a Fly";
} }
} }

View File

@ -26,25 +26,25 @@ package com.iluwatar.saga.orchestration;
* Class representing a service to book a hotel * Class representing a service to book a hotel
*/ */
public class HotelBookingService extends Service<String> { public class HotelBookingService extends Service<String> {
@Override @Override
public String getName() { public String getName() {
return "booking a Hotel"; 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 return super.rollback(value);
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);
}
} }

View File

@ -23,28 +23,31 @@
package com.iluwatar.saga.orchestration; 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 * @param <K> is type for passing params
*/ */
public interface Chapter<K> { public interface OrchestrationChapter<K> {
/** /**
* @return service name. * @return service name.
*/ */
String getName(); String getName();
/** /**
* The operation executed in general case. * The operation executed in general case.
* @param value incoming value *
* @return result {@link ChapterResult} * @param value incoming value
*/ * @return result {@link ChapterResult}
ChapterResult<K> process(K value); */
ChapterResult<K> process(K value);
/** /**
* The operation executed in rollback case. * The operation executed in rollback case.
* @param value incoming value *
* @return result {@link ChapterResult} * @param value incoming value
*/ * @return result {@link ChapterResult}
ChapterResult<K> rollback(K value); */
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. * Class representing a service to init a new order.
*/ */
public class OrderService extends Service<String> { public class OrderService extends Service<String> {
@Override @Override
public String getName() { public String getName() {
return "init an order"; return "init an order";
} }
} }

View File

@ -28,49 +28,56 @@ import java.util.List;
/** /**
* Saga representation. * Saga representation.
* Saga consists of chapters. * Saga consists of chapters.
* Every Chapter is executed by a certain service. * Every ChoreographyChapter is executed by a certain service.
*/ */
public class Saga { 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 String getName() {
public Saga chapter(String name) { return 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;
}
} }
}
} }

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 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 * This is an analog of transaction in a database but in terms of microservices architecture this is executed
* in a distributed environment * in a distributed environment
* * <p>
* A saga is a sequence of local transactions in a certain context. If one transaction fails for some reason, * 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. * the saga executes compensating transactions(rollbacks) to undo the impact of the preceding transactions.
* * <p>
* In this approach, there is an orchestrator @see {@link SagaOrchestrator} that manages all the transactions and directs * 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 participant services to execute local transactions based on events.
* The major difference with choreography saga is an ability to handle crashed services * 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) * (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 * @see Service
*/ */
public class SagaApplication { 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 goodOrder = sagaOrchestrator.execute("good_order");
Saga.Result badOrder = sagaOrchestrator.execute("bad_order"); Saga.Result badOrder = sagaOrchestrator.execute("bad_order");
Saga.Result crashedOrder = sagaOrchestrator.execute("crashed_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() { private static Saga newSaga() {
return Saga return Saga
.create() .create()
.chapter("init an order") .chapter("init an order")
.chapter("booking a Fly") .chapter("booking a Fly")
.chapter("booking a Hotel") .chapter("booking a Hotel")
.chapter("withdrawing Money"); .chapter("withdrawing Money");
} }
private static ServiceDiscoveryService serviceDiscovery() { private static ServiceDiscoveryService serviceDiscovery() {
return return
new ServiceDiscoveryService() new ServiceDiscoveryService()
.discover(new OrderService()) .discover(new OrderService())
.discover(new FlyBookingService()) .discover(new FlyBookingService())
.discover(new HotelBookingService()) .discover(new HotelBookingService())
.discover(new WithdrawMoneyService()); .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. * the participant services to execute local transactions based on events.
*/ */
public class SagaOrchestrator { public class SagaOrchestrator {
private static final Logger logger = LoggerFactory.getLogger(SagaOrchestrator.class); private static final Logger LOGGER = LoggerFactory.getLogger(SagaOrchestrator.class);
private final Saga saga; private final Saga saga;
private final ServiceDiscoveryService sd; private final ServiceDiscoveryService sd;
private final CurrentState state; private final CurrentState state;
public SagaOrchestrator(Saga saga, ServiceDiscoveryService sd) { /**
this.saga = saga; * Create a new service to orchetrate sagas
this.sd = sd; * @param saga saga to process
this.state = new CurrentState(); * @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)) { private static class CurrentState {
return state.isForward() ? FINISHED : result == CRASHED ? CRASHED : ROLLBACK; int currentNumber;
} boolean isForward;
}
void cleanUp() {
currentNumber = 0;
isForward = true;
}
CurrentState() {
this.currentNumber = 0;
this.isForward = true;
} }
private static class CurrentState { boolean isForward() {
int currentNumber; return isForward;
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;
}
} }
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 * 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 * @param <K> type of incoming param
*/ */
public abstract class Service<K> implements Chapter<K> { public abstract class Service<K> implements OrchestrationChapter<K> {
protected static final Logger logger = LoggerFactory.getLogger(Service.class); protected static final Logger LOGGER = LoggerFactory.getLogger(Service.class);
@Override @Override
public abstract String getName(); public abstract String getName();
@Override @Override
public ChapterResult<K> process(K value) { public ChapterResult<K> process(K value) {
logger.info("The chapter '{}' has been started. The data {} has been stored or calculated successfully", LOGGER.info("The chapter '{}' has been started. The data {} has been stored or calculated successfully",
getName(),value); getName(), value);
return ChapterResult.success(value); return ChapterResult.success(value);
} }
@Override @Override
public ChapterResult<K> rollback(K value) { public ChapterResult<K> rollback(K value) {
logger.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully", LOGGER.info("The Rollback for a chapter '{}' has been started. The data {} has been rollbacked successfully",
getName(),value); getName(), value);
return ChapterResult.success(value); return ChapterResult.success(value);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,112 +28,118 @@ import org.junit.Test;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/**
* test to test orchestration logic
*/
public class SagaOrchestratorInternallyTest { public class SagaOrchestratorInternallyTest {
private List<String> records = new ArrayList<>(); private List<String> records = new ArrayList<>();
@Test @Test
public void executeTest() { public void executeTest() {
SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery()); SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery());
Saga.Result result = sagaOrchestrator.execute(1); Saga.Result result = sagaOrchestrator.execute(1);
Assert.assertEquals(result, Saga.Result.ROLLBACK); Assert.assertEquals(result, Saga.Result.ROLLBACK);
Assert.assertArrayEquals( Assert.assertArrayEquals(
records.toArray(new String[]{}), records.toArray(new String[]{}),
new String[]{"+1","+2","+3","+4","-4","-3","-2","-1"}); 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() { @Override
return Saga public ChapterResult<Integer> process(Integer value) {
.create() records.add("+1");
.chapter("1") return ChapterResult.success(value);
.chapter("2")
.chapter("3")
.chapter("4");
} }
private ServiceDiscoveryService serviceDiscovery() { @Override
return public ChapterResult<Integer> rollback(Integer value) {
new ServiceDiscoveryService() records.add("-1");
.discover(new Service1()) return ChapterResult.success(value);
.discover(new Service2()) }
.discover(new Service3()) }
.discover(new Service4());
class Service2 extends Service<Integer> {
@Override
public String getName() {
return "2";
} }
class Service1 extends Service<Integer> { @Override
public ChapterResult<Integer> process(Integer value) {
@Override records.add("+2");
public String getName() { return ChapterResult.success(value);
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 ChapterResult<Integer> rollback(Integer value) {
records.add("-2");
return ChapterResult.success(value);
}
}
@Override class Service3 extends Service<Integer> {
public String getName() {
return "2";
}
@Override
public ChapterResult<Integer> process(Integer value) {
records.add("+2");
return ChapterResult.success(value);
}
@Override @Override
public ChapterResult<Integer> rollback(Integer value) { public String getName() {
records.add("-2"); return "3";
return ChapterResult.success(value);
}
} }
class Service3 extends Service<Integer> { @Override
public ChapterResult<Integer> process(Integer value) {
@Override records.add("+3");
public String getName() { return ChapterResult.success(value);
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 ChapterResult<Integer> rollback(Integer value) {
@Override records.add("-3");
public String getName() { return ChapterResult.success(value);
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);
}
} }
}
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.Assert;
import org.junit.Test; import org.junit.Test;
/**
* test to check general logic
*/
public class SagaOrchestratorTest { public class SagaOrchestratorTest {
@Test @Test
public void execute() { public void execute() {
SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery()); SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery());
Saga.Result badOrder = sagaOrchestrator.execute("bad_order"); Saga.Result badOrder = sagaOrchestrator.execute("bad_order");
Saga.Result crashedOrder = sagaOrchestrator.execute("crashed_order"); Saga.Result crashedOrder = sagaOrchestrator.execute("crashed_order");
Assert.assertEquals(badOrder, Saga.Result.ROLLBACK); Assert.assertEquals(badOrder, Saga.Result.ROLLBACK);
Assert.assertEquals(crashedOrder, Saga.Result.CRASHED); Assert.assertEquals(crashedOrder, Saga.Result.CRASHED);
} }
private static Saga newSaga() { private static Saga newSaga() {
return Saga return Saga
.create() .create()
.chapter("init an order") .chapter("init an order")
.chapter("booking a Fly") .chapter("booking a Fly")
.chapter("booking a Hotel") .chapter("booking a Hotel")
.chapter("withdrawing Money"); .chapter("withdrawing Money");
} }
private static ServiceDiscoveryService serviceDiscovery() { private static ServiceDiscoveryService serviceDiscovery() {
return return
new ServiceDiscoveryService() new ServiceDiscoveryService()
.discover(new OrderService()) .discover(new OrderService())
.discover(new FlyBookingService()) .discover(new FlyBookingService())
.discover(new HotelBookingService()) .discover(new HotelBookingService())
.discover(new WithdrawMoneyService()); .discover(new WithdrawMoneyService());
} }
} }