change according to cgeckstyle
This commit is contained in:
@ -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)
|
@ -23,7 +23,8 @@
|
|||||||
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="http://maven.apache.org/POM/4.0.0"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
|
@ -24,14 +24,15 @@ 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
|
* @param saga incoming saga
|
||||||
* @return saga result
|
* @return saga result
|
||||||
*/
|
*/
|
||||||
@ -44,6 +45,7 @@ public interface Chapter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The operation executed in general case.
|
* The operation executed in general case.
|
||||||
|
*
|
||||||
* @param saga incoming saga
|
* @param saga incoming saga
|
||||||
* @return result {@link Saga}
|
* @return result {@link Saga}
|
||||||
*/
|
*/
|
||||||
@ -51,6 +53,7 @@ public interface Chapter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The operation executed in rollback case.
|
* The operation executed in rollback case.
|
||||||
|
*
|
||||||
* @param saga incoming saga
|
* @param saga incoming saga
|
||||||
* @return result {@link Saga}
|
* @return result {@link Saga}
|
||||||
*/
|
*/
|
@ -29,7 +29,7 @@ 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 {
|
||||||
|
|
||||||
@ -42,17 +42,37 @@ public class Saga {
|
|||||||
public static Saga create() {
|
public static Saga create() {
|
||||||
return new Saga();
|
return new Saga();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get resuzlt of saga
|
||||||
|
*
|
||||||
|
* @return result of saga @see {@link SagaResult}
|
||||||
|
*/
|
||||||
public SagaResult getResult() {
|
public SagaResult getResult() {
|
||||||
return !finished ?
|
if (finished) {
|
||||||
SagaResult.PROGRESS :
|
return forward
|
||||||
forward ?
|
? SagaResult.FINISHED
|
||||||
SagaResult.FINISHED :
|
: SagaResult.ROLLBACKED;
|
||||||
SagaResult.ROLLBACKED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return SagaResult.PROGRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add chapter to saga
|
||||||
|
* @param name chapter name
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
public Saga chapter(String name) {
|
public Saga chapter(String name) {
|
||||||
this.chapters.add(new Chapter(name));
|
this.chapters.add(new Chapter(name));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set value to last chapter
|
||||||
|
* @param value invalue
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
public Saga setInValue(Object value) {
|
public Saga setInValue(Object value) {
|
||||||
if (chapters.isEmpty()) {
|
if (chapters.isEmpty()) {
|
||||||
return this;
|
return this;
|
||||||
@ -60,12 +80,27 @@ public class Saga {
|
|||||||
chapters.get(chapters.size() - 1).setInValue(value);
|
chapters.get(chapters.size() - 1).setInValue(value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get value from current chapter
|
||||||
|
* @return value
|
||||||
|
*/
|
||||||
public Object getCurrentValue() {
|
public Object getCurrentValue() {
|
||||||
return chapters.get(pos).getInValue();
|
return chapters.get(pos).getInValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set value to current chapter
|
||||||
|
* @param value to set
|
||||||
|
*/
|
||||||
public void setCurrentValue(Object value) {
|
public void setCurrentValue(Object value) {
|
||||||
chapters.get(pos).setInValue(value);
|
chapters.get(pos).setInValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set status for current chapter
|
||||||
|
* @param result to set
|
||||||
|
*/
|
||||||
public void setCurrentStatus(ChapterResult result) {
|
public void setCurrentStatus(ChapterResult result) {
|
||||||
chapters.get(pos).setResult(result);
|
chapters.get(pos).setResult(result);
|
||||||
}
|
}
|
||||||
@ -73,9 +108,11 @@ public class Saga {
|
|||||||
void setFinished(boolean finished) {
|
void setFinished(boolean finished) {
|
||||||
this.finished = finished;
|
this.finished = finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isForward() {
|
boolean isForward() {
|
||||||
return forward;
|
return forward;
|
||||||
}
|
}
|
||||||
|
|
||||||
int forward() {
|
int forward() {
|
||||||
return ++pos;
|
return ++pos;
|
||||||
}
|
}
|
||||||
@ -86,7 +123,7 @@ public class Saga {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Saga() {
|
private Saga() {
|
||||||
this.chapters = new ArrayList<>();
|
this.chapters = new ArrayList<>();
|
||||||
this.pos = 0;
|
this.pos = 0;
|
||||||
this.forward = true;
|
this.forward = true;
|
||||||
@ -101,6 +138,7 @@ public class Saga {
|
|||||||
boolean isPresent() {
|
boolean isPresent() {
|
||||||
return pos >= 0 && pos < chapters.size();
|
return pos >= 0 && pos < chapters.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isCurrentSuccess() {
|
boolean isCurrentSuccess() {
|
||||||
return chapters.get(pos).isSuccess();
|
return chapters.get(pos).isSuccess();
|
||||||
}
|
}
|
||||||
@ -130,30 +168,48 @@ public class Saga {
|
|||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set result
|
||||||
|
* @param result {@link ChapterResult}
|
||||||
|
*/
|
||||||
public void setResult(ChapterResult result) {
|
public void setResult(ChapterResult result) {
|
||||||
this.result = result;
|
this.result = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the result for chapter is good
|
||||||
|
* @return true if is good otherwise bad
|
||||||
|
*/
|
||||||
public boolean isSuccess() {
|
public boolean isSuccess() {
|
||||||
return result == ChapterResult.SUCCESS;
|
return result == ChapterResult.SUCCESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* result for chapter
|
||||||
|
*/
|
||||||
public enum ChapterResult {
|
public enum ChapterResult {
|
||||||
INIT, SUCCESS, ROLLBACK
|
INIT, SUCCESS, ROLLBACK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* result for saga
|
||||||
|
*/
|
||||||
public enum SagaResult {
|
public enum SagaResult {
|
||||||
PROGRESS, FINISHED, ROLLBACKED
|
PROGRESS, FINISHED, ROLLBACKED
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Saga{" +
|
return "Saga{"
|
||||||
"chapters=" + Arrays.toString(chapters.toArray()) +
|
+ "chapters="
|
||||||
", pos=" + pos +
|
+ Arrays.toString(chapters.toArray())
|
||||||
", forward=" + forward +
|
+ ", pos="
|
||||||
'}';
|
+ pos
|
||||||
|
+ ", forward="
|
||||||
|
+ forward
|
||||||
|
+ '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,14 +44,17 @@ 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* main method
|
||||||
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
ServiceDiscoveryService sd = serviceDiscovery();
|
ServiceDiscoveryService sd = serviceDiscovery();
|
||||||
Chapter service = sd.findAny();
|
ChoreographyChapter service = sd.findAny();
|
||||||
Saga goodOrderSaga = service.execute(newSaga("good_order"));
|
Saga goodOrderSaga = service.execute(newSaga("good_order"));
|
||||||
Saga badOrderSaga = service.execute(newSaga("bad_order"));
|
Saga badOrderSaga = service.execute(newSaga("bad_order"));
|
||||||
logger.info("orders: goodOrder is {}, badOrder is {}",
|
LOGGER.info("orders: goodOrder is {}, badOrder is {}",
|
||||||
goodOrderSaga.getResult(), badOrderSaga.getResult());
|
goodOrderSaga.getResult(), badOrderSaga.getResult());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,12 +25,14 @@ 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;
|
||||||
|
|
||||||
@ -67,13 +69,17 @@ public abstract class Service implements Chapter {
|
|||||||
Saga finalNextSaga = nextSaga;
|
Saga finalNextSaga = nextSaga;
|
||||||
|
|
||||||
return sd.find(chapterName).map(ch -> ch.execute(finalNextSaga))
|
return sd.find(chapterName).map(ch -> ch.execute(finalNextSaga))
|
||||||
.orElseThrow(RuntimeException::new);
|
.orElseThrow(serviceNotFoundException(chapterName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Supplier<RuntimeException> serviceNotFoundException(String chServiceName) {
|
||||||
|
return () -> new RuntimeException(String.format("the service %s has not been found", chServiceName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Saga process(Saga saga) {
|
public Saga process(Saga saga) {
|
||||||
Object inValue = saga.getCurrentValue();
|
Object inValue = saga.getCurrentValue();
|
||||||
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(), inValue);
|
getName(), inValue);
|
||||||
saga.setCurrentStatus(Saga.ChapterResult.SUCCESS);
|
saga.setCurrentStatus(Saga.ChapterResult.SUCCESS);
|
||||||
saga.setCurrentValue(inValue);
|
saga.setCurrentValue(inValue);
|
||||||
@ -83,7 +89,7 @@ public abstract class Service implements Chapter {
|
|||||||
@Override
|
@Override
|
||||||
public Saga rollback(Saga saga) {
|
public Saga rollback(Saga saga) {
|
||||||
Object inValue = saga.getCurrentValue();
|
Object inValue = saga.getCurrentValue();
|
||||||
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(), inValue);
|
getName(), inValue);
|
||||||
|
|
||||||
saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK);
|
saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK);
|
||||||
@ -94,7 +100,7 @@ public abstract class Service implements Chapter {
|
|||||||
private boolean isSagaFinished(Saga saga) {
|
private boolean isSagaFinished(Saga saga) {
|
||||||
if (!saga.isPresent()) {
|
if (!saga.isPresent()) {
|
||||||
saga.setFinished(true);
|
saga.setFinished(true);
|
||||||
logger.info(" the saga has been finished with {} status", saga.getResult());
|
LOGGER.info(" the saga has been finished with {} status", saga.getResult());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -32,22 +32,23 @@ 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
|
* @return any service
|
||||||
* @throws NoSuchElementException if no elements further
|
* @throws NoSuchElementException if no elements further
|
||||||
*/
|
*/
|
||||||
public Chapter findAny(){
|
public ChoreographyChapter findAny() {
|
||||||
return services.values().iterator().next();
|
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;
|
||||||
}
|
}
|
||||||
|
@ -42,8 +42,8 @@ public class WithdrawMoneyService extends Service {
|
|||||||
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;
|
||||||
|
@ -24,6 +24,7 @@ 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> {
|
||||||
@ -51,6 +52,9 @@ public class ChapterResult<K> {
|
|||||||
return new ChapterResult<>(val, State.FAILURE);
|
return new ChapterResult<>(val, State.FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* state for chapter
|
||||||
|
*/
|
||||||
public enum State {
|
public enum State {
|
||||||
SUCCESS, FAILURE
|
SUCCESS, FAILURE
|
||||||
}
|
}
|
||||||
|
@ -35,14 +35,14 @@ public class HotelBookingService extends Service<String> {
|
|||||||
@Override
|
@Override
|
||||||
public ChapterResult<String> rollback(String value) {
|
public ChapterResult<String> rollback(String value) {
|
||||||
if (value.equals("crashed_order")) {
|
if (value.equals("crashed_order")) {
|
||||||
logger.info("The Rollback for a chapter '{}' has been started. " +
|
LOGGER.info("The Rollback for a chapter '{}' has been started. "
|
||||||
"The data {} has been failed.The saga has been crashed.",
|
+ "The data {} has been failed.The saga has been crashed.",
|
||||||
getName(), value);
|
getName(), value);
|
||||||
|
|
||||||
return ChapterResult.failure(value);
|
return ChapterResult.failure(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 super.rollback(value);
|
return super.rollback(value);
|
||||||
|
@ -23,10 +23,11 @@
|
|||||||
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.
|
||||||
@ -35,6 +36,7 @@ public interface Chapter<K> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The operation executed in general case.
|
* The operation executed in general case.
|
||||||
|
*
|
||||||
* @param value incoming value
|
* @param value incoming value
|
||||||
* @return result {@link ChapterResult}
|
* @return result {@link ChapterResult}
|
||||||
*/
|
*/
|
||||||
@ -42,6 +44,7 @@ public interface Chapter<K> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The operation executed in rollback case.
|
* The operation executed in rollback case.
|
||||||
|
*
|
||||||
* @param value incoming value
|
* @param value incoming value
|
||||||
* @return result {@link ChapterResult}
|
* @return result {@link ChapterResult}
|
||||||
*/
|
*/
|
@ -28,13 +28,14 @@ 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() {
|
|
||||||
|
private Saga() {
|
||||||
this.chapters = new ArrayList<>();
|
this.chapters = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,10 +59,16 @@ public class Saga {
|
|||||||
return new Saga();
|
return new Saga();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* result for saga
|
||||||
|
*/
|
||||||
public enum Result {
|
public enum Result {
|
||||||
FINISHED, ROLLBACK, CRASHED
|
FINISHED, ROLLBACK, CRASHED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* class represents chapter name
|
||||||
|
*/
|
||||||
public static class Chapter {
|
public static class Chapter {
|
||||||
String name;
|
String name;
|
||||||
|
|
||||||
|
@ -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,8 +46,11 @@ 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* method to show common saga logic
|
||||||
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery());
|
SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery());
|
||||||
|
|
||||||
@ -54,7 +58,7 @@ public class SagaApplication {
|
|||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,12 +34,17 @@ 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;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
public SagaOrchestrator(Saga saga, ServiceDiscoveryService sd) {
|
||||||
this.saga = saga;
|
this.saga = saga;
|
||||||
this.sd = sd;
|
this.sd = sd;
|
||||||
@ -56,14 +61,14 @@ public class SagaOrchestrator {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <K> Saga.Result execute(K value) {
|
public <K> Saga.Result execute(K value) {
|
||||||
state.cleanUp();
|
state.cleanUp();
|
||||||
logger.info(" The new saga is about to start");
|
LOGGER.info(" The new saga is about to start");
|
||||||
Saga.Result result = FINISHED;
|
Saga.Result result = FINISHED;
|
||||||
K tempVal = value;
|
K tempVal = value;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
int next = state.current();
|
int next = state.current();
|
||||||
Saga.Chapter ch = saga.get(next);
|
Saga.Chapter ch = saga.get(next);
|
||||||
Optional<Chapter> srvOpt = sd.find(ch.name);
|
Optional<OrchestrationChapter> srvOpt = sd.find(ch.name);
|
||||||
|
|
||||||
if (!srvOpt.isPresent()) {
|
if (!srvOpt.isPresent()) {
|
||||||
state.directionToBack();
|
state.directionToBack();
|
||||||
@ -71,7 +76,7 @@ public class SagaOrchestrator {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Chapter srv = srvOpt.get();
|
OrchestrationChapter srv = srvOpt.get();
|
||||||
|
|
||||||
if (state.isForward()) {
|
if (state.isForward()) {
|
||||||
ChapterResult processRes = srv.process(tempVal);
|
ChapterResult processRes = srv.process(tempVal);
|
||||||
|
@ -27,11 +27,12 @@ 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();
|
||||||
@ -39,14 +40,14 @@ public abstract class Service<K> implements Chapter<K> {
|
|||||||
|
|
||||||
@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);
|
||||||
}
|
}
|
||||||
|
@ -30,14 +30,14 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,8 +34,8 @@ public class WithdrawMoneyService extends Service<String> {
|
|||||||
@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);
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,10 @@ 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() {
|
||||||
|
@ -25,13 +25,16 @@ 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"));
|
||||||
|
|
||||||
|
@ -26,6 +26,9 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* empty test
|
||||||
|
*/
|
||||||
public class SagaApplicationTest {
|
public class SagaApplicationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -28,6 +28,9 @@ 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<>();
|
||||||
@ -86,6 +89,7 @@ public class SagaOrchestratorInternallyTest {
|
|||||||
public String getName() {
|
public String getName() {
|
||||||
return "2";
|
return "2";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChapterResult<Integer> process(Integer value) {
|
public ChapterResult<Integer> process(Integer value) {
|
||||||
records.add("+2");
|
records.add("+2");
|
||||||
@ -105,6 +109,7 @@ public class SagaOrchestratorInternallyTest {
|
|||||||
public String getName() {
|
public String getName() {
|
||||||
return "3";
|
return "3";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChapterResult<Integer> process(Integer value) {
|
public ChapterResult<Integer> process(Integer value) {
|
||||||
records.add("+3");
|
records.add("+3");
|
||||||
@ -124,6 +129,7 @@ public class SagaOrchestratorInternallyTest {
|
|||||||
public String getName() {
|
public String getName() {
|
||||||
return "4";
|
return "4";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChapterResult<Integer> process(Integer value) {
|
public ChapterResult<Integer> process(Integer value) {
|
||||||
records.add("+4");
|
records.add("+4");
|
||||||
|
@ -25,6 +25,9 @@ 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
|
||||||
|
Reference in New Issue
Block a user