This is an automated email from the ASF dual-hosted git repository. ningjiang pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-servicecomb-saga.git
commit 0eb405e9b08000317a7412aeabe8ae41e0518a8a Author: seanyinx <sean....@huawei.com> AuthorDate: Sat Dec 23 15:24:54 2017 +0800 SCB-96 invoked compensation method annotated with Compensable Signed-off-by: seanyinx <sean....@huawei.com> --- .../saga/omega/context/OmegaContext.java | 57 +++++++++++++++++++++- omega/omega-spring-tx/pom.xml | 9 ++-- .../spring/TransactionInterceptionTest.java | 42 ++++++++++++++-- .../spring/TransactionalUserService.java | 14 ++++-- .../saga/omega/transaction/spring/User.java | 26 +++++++++- .../omega/transaction/MessageDeserializer.java} | 22 ++------- .../saga/omega/transaction/MessageHandler.java} | 22 ++------- .../saga/omega/transaction/TransactionAspect.java | 12 +++-- .../transaction/annotations/Compensable.java} | 27 ++++------ 9 files changed, 158 insertions(+), 73 deletions(-) diff --git a/omega/omega-context/src/main/java/io/servicecomb/saga/omega/context/OmegaContext.java b/omega/omega-context/src/main/java/io/servicecomb/saga/omega/context/OmegaContext.java index 9884349..8b41d9a 100644 --- a/omega/omega-context/src/main/java/io/servicecomb/saga/omega/context/OmegaContext.java +++ b/omega/omega-context/src/main/java/io/servicecomb/saga/omega/context/OmegaContext.java @@ -17,11 +17,16 @@ package io.servicecomb.saga.omega.context; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + public class OmegaContext { private final ThreadLocal<String> globalTxId = new ThreadLocal<>(); private final ThreadLocal<String> localTxId = new ThreadLocal<>(); private final ThreadLocal<String> parentTxId = new ThreadLocal<>(); - + private final Map<String, CompensationContext> compensationContexts = new ConcurrentHashMap<>(); public void setGlobalTxId(String txId) { globalTxId.set(txId); @@ -47,6 +52,44 @@ public class OmegaContext { this.parentTxId.set(parentTxId); } + // TODO: 2017/12/23 remove this context entry by the end of its corresponding global tx + public void addContext(String id, Object target, String compensationMethod, Object... args) { + compensationContexts.put(id, new CompensationContext(target, compensationMethod, args)); + } + + public void compensate(String globalTxId) { + CompensationContext compensationContext = compensationContexts.get(globalTxId); + + try { + invokeMethod(compensationContext); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException( + "Pre-checking for compensate method " + compensationContext.compensationMethod + " was somehow skipped", + e); + } + } + + private void invokeMethod(CompensationContext compensationContext) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + + Method method = compensationContext.target + .getClass() + .getDeclaredMethod(compensationContext.compensationMethod, argClasses(compensationContext)); + method.setAccessible(true); + + method.invoke(compensationContext.target, compensationContext.args); + } + + private Class<?>[] argClasses(CompensationContext compensationContext) { + Class<?>[] classes = new Class<?>[compensationContext.args.length]; + + for (int i = 0; i < compensationContext.args.length; i++) { + classes[i] = compensationContext.args[i].getClass(); + } + + return classes; + } + @Override public String toString() { return "OmegaContext{" + @@ -55,4 +98,16 @@ public class OmegaContext { ", parentTxId=" + parentTxId.get() + '}'; } + + private static final class CompensationContext { + private final Object target; + private final String compensationMethod; + private final Object[] args; + + private CompensationContext(Object target, String compensationMethod, Object... args) { + this.target = target; + this.compensationMethod = compensationMethod; + this.args = args; + } + } } diff --git a/omega/omega-spring-tx/pom.xml b/omega/omega-spring-tx/pom.xml index b88f231..6019650 100644 --- a/omega/omega-spring-tx/pom.xml +++ b/omega/omega-spring-tx/pom.xml @@ -33,10 +33,6 @@ <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-data-jpa</artifactId> - </dependency> - <dependency> <groupId>io.servicecomb.saga</groupId> <artifactId>omega-context</artifactId> </dependency> @@ -71,6 +67,11 @@ <artifactId>h2</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + <scope>test</scope> + </dependency> </dependencies> diff --git a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionInterceptionTest.java b/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionInterceptionTest.java index 0c4fbab..27ee479 100644 --- a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionInterceptionTest.java +++ b/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionInterceptionTest.java @@ -19,33 +19,40 @@ package io.servicecomb.saga.omega.transaction.spring; import static com.seanyinx.github.unit.scaffolding.Randomness.uniquify; import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit4.SpringRunner; import io.servicecomb.saga.omega.context.OmegaContext; +import io.servicecomb.saga.omega.transaction.MessageHandler; import io.servicecomb.saga.omega.transaction.MessageSender; import io.servicecomb.saga.omega.transaction.MessageSerializer; import io.servicecomb.saga.omega.transaction.spring.TransactionInterceptionTest.MessageConfig; @RunWith(SpringRunner.class) @SpringBootTest(classes = {TransactionTestMain.class, MessageConfig.class}) +@AutoConfigureMockMvc public class TransactionInterceptionTest { private static final String TX_STARTED_EVENT = "TxStartedEvent"; private static final String TX_ENDED_EVENT = "TxEndedEvent"; - private final String globalTxId = UUID.randomUUID().toString(); + private static final String globalTxId = UUID.randomUUID().toString(); private final String localTxId = UUID.randomUUID().toString(); private final String parentTxId = UUID.randomUUID().toString(); private final String username = uniquify("username"); @@ -60,6 +67,12 @@ public class TransactionInterceptionTest { @Autowired private OmegaContext omegaContext; + @Autowired + private UserRepository userRepository; + + @Autowired + private MessageHandler messageHandler; + @Before public void setUp() throws Exception { omegaContext.setGlobalTxId(globalTxId); @@ -67,9 +80,14 @@ public class TransactionInterceptionTest { omegaContext.setParentTxId(parentTxId); } + @After + public void tearDown() throws Exception { + messages.clear(); + } + @Test - public void sendsUserToRemote_BeforeTransaction() throws Exception { - userService.add(new User(username, email)); + public void sendsUserToRemote_AroundTransaction() throws Exception { + User user = userService.add(new User(username, email)); assertEquals( asList( @@ -77,6 +95,19 @@ public class TransactionInterceptionTest { txEndedEvent(globalTxId, localTxId, parentTxId)), toString(messages) ); + + User actual = userRepository.findOne(user.id()); + assertThat(actual, is(user)); + } + + @Test + public void compensateOnTransactionException() throws Exception { + User user = userService.add(new User(username, email)); + + messageHandler.onReceive("to be compensated".getBytes()); + + User actual = userRepository.findOne(user.id()); + assertThat(actual, is(nullValue())); } private List<String> toString(List<byte[]> messages) { @@ -115,6 +146,11 @@ public class TransactionInterceptionTest { event.parentTxId()).getBytes(); }; } + + @Bean + MessageHandler handler(OmegaContext omegaContext) { + return bytes -> omegaContext.compensate(globalTxId); + } } private static String txStartedEvent(String globalTxId, diff --git a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java b/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java index 1d17c6c..74cb59e 100644 --- a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java +++ b/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java @@ -17,11 +17,11 @@ package io.servicecomb.saga.omega.transaction.spring; -import javax.transaction.Transactional; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import io.servicecomb.saga.omega.transaction.annotations.Compensable; + @Component class TransactionalUserService { private final UserRepository userRepository; @@ -31,8 +31,12 @@ class TransactionalUserService { this.userRepository = userRepository; } - @Transactional - void add(User user) { - userRepository.save(user); + @Compensable(compensationMethod = "delete") + User add(User user) { + return userRepository.save(user); + } + + void delete(User user) { + userRepository.delete(user.id()); } } diff --git a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/User.java b/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/User.java index a90d1d6..6b2e55f 100644 --- a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/User.java +++ b/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/User.java @@ -17,6 +17,8 @@ package io.servicecomb.saga.omega.transaction.spring; +import java.util.Objects; + import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @@ -30,12 +32,15 @@ public class User { private String username; private String email; + User() { + } + User(String username, String email) { this.username = username; this.email = email; } - public long id() { + long id() { return id; } @@ -46,4 +51,23 @@ public class User { String email() { return email; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + User user = (User) o; + return id == user.id && + Objects.equals(username, user.username) && + Objects.equals(email, user.email); + } + + @Override + public int hashCode() { + return Objects.hash(id, username, email); + } } diff --git a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/MessageDeserializer.java similarity index 61% copy from omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java copy to omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/MessageDeserializer.java index 1d17c6c..d4315b3 100644 --- a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java +++ b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/MessageDeserializer.java @@ -15,24 +15,8 @@ * limitations under the License. */ -package io.servicecomb.saga.omega.transaction.spring; +package io.servicecomb.saga.omega.transaction; -import javax.transaction.Transactional; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -class TransactionalUserService { - private final UserRepository userRepository; - - @Autowired - TransactionalUserService(UserRepository userRepository) { - this.userRepository = userRepository; - } - - @Transactional - void add(User user) { - userRepository.save(user); - } +public interface MessageDeserializer { + <T> T deserialize(byte[] message); } diff --git a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/MessageHandler.java similarity index 61% copy from omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java copy to omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/MessageHandler.java index 1d17c6c..e954381 100644 --- a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java +++ b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/MessageHandler.java @@ -15,24 +15,8 @@ * limitations under the License. */ -package io.servicecomb.saga.omega.transaction.spring; +package io.servicecomb.saga.omega.transaction; -import javax.transaction.Transactional; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -class TransactionalUserService { - private final UserRepository userRepository; - - @Autowired - TransactionalUserService(UserRepository userRepository) { - this.userRepository = userRepository; - } - - @Transactional - void add(User user) { - userRepository.save(user); - } +public interface MessageHandler { + void onReceive(byte[] message); } diff --git a/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/TransactionAspect.java b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/TransactionAspect.java index e17861d..6d13a04 100644 --- a/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/TransactionAspect.java +++ b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/TransactionAspect.java @@ -28,6 +28,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.servicecomb.saga.omega.context.OmegaContext; +import io.servicecomb.saga.omega.transaction.annotations.Compensable; @Aspect public class TransactionAspect { @@ -42,10 +43,15 @@ public class TransactionAspect { this.postTransactionInterceptor = new PostTransactionInterceptor(sender, serializer); } - @Around("execution(@javax.transaction.Transactional * *(..))") - Object advise(ProceedingJoinPoint joinPoint) throws Throwable { + @Around("execution(@io.servicecomb.saga.omega.transaction.annotations.Compensable * *(..)) && @annotation(compensable)") + Object advise(ProceedingJoinPoint joinPoint, Compensable compensable) throws Throwable { Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); - LOG.debug("Intercepting transactional method {} with context {}", method.toString(), context); + LOG.debug("Intercepting compensable method {} with context {}", method.toString(), context); + + context.addContext(context.globalTxId(), + joinPoint.getTarget(), + compensable.compensationMethod(), + joinPoint.getArgs()); preIntercept(joinPoint); Object result = joinPoint.proceed(); diff --git a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/annotations/Compensable.java similarity index 62% copy from omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java copy to omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/annotations/Compensable.java index 1d17c6c..bb86666 100644 --- a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java +++ b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/annotations/Compensable.java @@ -15,24 +15,15 @@ * limitations under the License. */ -package io.servicecomb.saga.omega.transaction.spring; +package io.servicecomb.saga.omega.transaction.annotations; -import javax.transaction.Transactional; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -class TransactionalUserService { - private final UserRepository userRepository; - - @Autowired - TransactionalUserService(UserRepository userRepository) { - this.userRepository = userRepository; - } - - @Transactional - void add(User user) { - userRepository.save(user); - } +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Compensable { + String compensationMethod(); } -- To stop receiving notification emails like this one, please contact "commits@servicecomb.apache.org" <commits@servicecomb.apache.org>.