I have scheduller:
#Component
public class MyScheduler {
private static final long INIT_DELAY = 1L;
private static final long DELAY = 10L;
private final UserService userService;
public MyScheduler(UserService userService) {
this.userService = userService;
}
#EventListener(ApplicationReadyEvent.class)
public void schedule() {
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleWithFixedDelay(this::process, INIT_DELAY, DELAY, TimeUnit.SECONDS);
}
private void process() {
userService.process(new User("Bill", 20));
}
}
In UserService I save new user and throw exception:
#Slf4j
#Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public void process(User user) {
log.info("Start process...");
userRepository.save(user);
methodWithException();
log.info("End process...");
}
private void methodWithException() {
throw new RuntimeException();
}
}
As a result, the user is saved despite the exception. To fix this I can apply several ways:
1) Add #Transactional above private void process() and change this method to public
2) Add #Transactional above public void process(User user) method in UserService
In first case it not helps because process() witn #Transactional calls from the same class.
In second case it helps.
But if I add new Service, for example LogService:
#Service
public class LogServiceImpl implements LogService {
private final LogRepository logRepository;
public LogServiceImpl(LogRepository logRepository) {
this.logRepository = logRepository;
}
#Transactional
#Override
public Log save(Log log) {
return logRepository.save(log);
}
}
And change scheduler to:
#Component
public class MyScheduler {
private static final long INIT_DELAY = 1L;
private static final long DELAY = 10L;
private final UserService userService;
private final LogService logService;
public MyScheduler(UserService userService, LogService logService) {
this.userService = userService;
this.logService = logService;
}
#EventListener(ApplicationReadyEvent.class)
public void schedule() {
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleWithFixedDelay(this::process, INIT_DELAY, DELAY, TimeUnit.SECONDS);
}
private void process() {
User user = userService.process(new User("Bill", 20));
logService.save(new Log(user.getId(), new Date()));
}
}
Question:
userService.process call in one transaction and logService.save cals in another transaction. I need call bouth services in One transaction.
I see two ways:
1) inject logService to userService and call logService.save in userService.process method
2) Create new service for example SchedulerService with method process and inject userService and logService in this service. And call in bouth services in one transaction.
In first case I get new dependency in userService and this can violate area of responsibility in this service. Why should the service know to pull another service
In second case I need create additional service (one more class)
It would be ideal to be able to annotate the internal Schedulers method #Transactional annotation. I know that this can be done using cglib instead proxy but I use proxy.
Which approach would be better?
Imho, this is a good usecase for PlatformTransactionManager, with or without TransactionTemplate.
For this I'm going with a pure PlatformTransactionManager solution.
If you're on Spring Boot you'll have it as a Bean by default.
#Component
class MyScheduler {
private static final long INIT_DELAY = 1L;
private static final long DELAY = 10L;
private final PlatformTransactionManager txManager;
private final ConcurrencyService userService;
private final LogService logService;
MyScheduler(
final PlatformTransactionManager txManager,
final ConcurrencyService userService,
final LogService logService) {
this.txManager = txManager;
this.userService = userService;
this.logService = logService;
}
#EventListener(ApplicationReadyEvent.class)
public void schedule() {
final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleWithFixedDelay(this::process, INIT_DELAY, DELAY, TimeUnit.SECONDS);
}
private void process() {
final DefaultTransactionDefinition definition = new DefaultTransactionDefinition(PROPAGATION_REQUIRES_NEW);
final TransactionStatus tx = txManager.getTransaction(definition);
try {
final User user = userService.process(new User("Bill", 20));
logService.save(new Log(user.getId(), new Date()));
txManager.commit(tx);
} catch (final YourException e) {
txManager.rollback(tx);
}
}
}
Using TransactionTemplate will "eliminate" the need of explicitly invoking commit and rollback.
You can have TransactionTemplate as a Bean, or you can construct it manually from a PlatformTransactionManager, as I've done here.
final TransactionTemplate transactionTemplate = new TransactionTemplate(txManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(final TransactionStatus status) {
final User user = userService.process(new User("Bill", 20));
logService.save(new Log(user.getId(), new Date()));
}
});
Related
I have a job that looks like this:
#Named
public class MyCamelRouteBuilder extends RouteBuilder {
private static final String JOB_NAME = "abc";
private static final String JOB_METHOD_NAME = "xyz";
private final MyJob myJob;
#Inject
public MyCamelRouteBuilder(MyJob myJob) {
super();
this.myJob = myJob;
}
#Override
public void configure() {
fromF("direct:%s", JOB_NAME)
.routeId(JOB_NAME)
.bean(myJob, JOB_METHOD_NAME)
.end();
fromF("master:some_name_1/some_name_2:scheduler:%s?delay=%s", JOB_NAME, 1234)
.routeId("JobTimer")
.toF("direct:%s", JOB_NAME)
.end();
}
}
A very simplified version of the job class:
#Named
public class MyJob {
private MyJob() {}
}
public void xyz() {
}
}
This does work and it does gets triggered as expected.
The problem starts here:
Now, I also want to create a REST controller that will be able to trigger the exact same job. Something like this:
#Named
#RestController
#RequestMapping
#Validated
public class MyController {
private static final String JOB_NAME = "abc";
private final ProducerTemplate producerTemplate;
#Inject
public MyController(
ProducerTemplate producerTemplate
) {
this.producerTemplate = producerTemplate;
}
#PostMapping(path = "/my_endpoint")
public String run() throws Exception {
producerTemplate.requestBody("direct:" + JOB_NAME);
return "ok";
}
}
But once it reaches this line, the job is not triggered and the request call keeps hanging.
producerTemplate.requestBody("direct:" + JOB_NAME);
Any ideas?
The fix for my problem:
#Named
#RestController
#RequestMapping
#Validated
public class MyController {
private static final String JOB_NAME = "abc";
#Produce("direct:" + JOB_NAME)
private final ProducerTemplate producerTemplate;
private final CamelContext context;
#Inject
public MyController(
ProducerTemplate producerTemplate, CamelContext context
) {
this.producerTemplate = producerTemplate;
this.context = context;
}
#PostMapping(path = "/my_endpoint")
public String run() throws Exception {
Exchange exchange = new DefaultExchange(context);
producerTemplate.send(exchange);
return "ok";
}
}
Note: I already look at and tried some approaches on SO e.g. How to test Spring's declarative caching support on Spring Data repositories?, but as most of them old, I cannot make them work properly and I need a solution with the latest library versions. So, I would be appreciated if you have a look at the question and help.
#Service
#EnableCaching
#RequiredArgsConstructor
public class DemoServiceImpl implements DemoService {
private static final String CACHE_NAME = "demoCache";
private final LabelRepository labelRepository;
private final LabelTranslatableRepository translatableRepository;
private final LanguageService languageService;
#Override
public LabelDTO findByUuid(UUID uuid) {
final Label label = labelRepository.findByUuid(uuid)
.orElseThrow(() -> new EntityNotFoundException("Not found."));
final List<LabelTranslatable> translatableList = translatableRepository.findAllByEntityUuid(uuid);
return new LabelDTO(Pair.of(label.getUuid(), label.getKey()), translatableList);
}
}
I created the following Unit Test to test caching for the nethod above:
#EnableCaching
#ImportAutoConfiguration(classes = {
CacheAutoConfiguration.class,
RedisAutoConfiguration.class
})
#ExtendWith(MockitoExtension.class)
class TextLabelServiceImpl_deneme_Test {
#Autowired
private CacheManager cacheManager;
#InjectMocks
private LabelService labelService;
#Mock
private LabelRepository labelRepository;
#Mock
private LabelTranslatableRepository translatableRepository;
#Test
void test_Cache() {
UUID uuid = UUID.randomUUID();
final TextLabel textLabel = new TextLabel();
textLabel.setId(1);
textLabel.setKey("key1");
TextLabelTranslatable textLabelTranslatable = new TextLabelTranslatable();
textLabelTranslatable.setEntityUuid(uuid);
textLabelTranslatable.setLanguage(SupportedLanguage.fr);
textLabelTranslatable.setValue("value1");
final List<TextLabelTranslatable> translatableList = new ArrayList<>();
translatableList.add(textLabelTranslatable);
when(labelRepository.findByUuid(uuid)).thenReturn(Optional.of(textLabel));
when(translatableRepository.findAllByEntityUuid(uuid)).thenReturn(translatableList);
TextLabelDTO result1 = labelService.findByUuid(uuid);
TextLabelDTO result2 = labelService.findByUuid(uuid);
assertEquals(result1, result2);
Mockito.verify(translatableRepository, Mockito.times(1)).findAllByEntityUuid(uuid);
}
I am not sure if there is a missing part in my test, but at the last line (Mockito.verify()), it returns 2 instead of 1 that means caching not works. But it is working properly and there is a problem in my test I think. How should I complete the unit test to check the caching properly?
You need to annotate the service class method with #Cacheable. Try to follow the code in this tutorial. The following test code works as expected
#Import({CacheConfig.class, DemoServiceImpl.class})
#ExtendWith(SpringExtension.class)
#EnableCaching
#ImportAutoConfiguration(classes = {
CacheAutoConfiguration.class,
RedisAutoConfiguration.class
})
class DemoServiceImplTest {
#MockBean
private LabelRepository labelRepository;
#Autowired
private DemoServiceImpl demoService;
#Autowired
private CacheManager cacheManager;
#TestConfiguration
static class EmbeddedRedisConfiguration {
private final RedisServer redisServer;
public EmbeddedRedisConfiguration() {
this.redisServer = new RedisServer();
}
#PostConstruct
public void startRedis() {
redisServer.start();
}
#PreDestroy
public void stopRedis() {
this.redisServer.stop();
}
}
#Test
void givenRedisCaching_whenFindItemById_thenItemReturnedFromCache() {
UUID id = UUID.randomUUID();
Label aLabel = new Label(id, "label");
Mockito.when(labelRepository.findById(id)).thenReturn(Optional.of(aLabel));
Label labelCacheMiss = demoService.findByUuid(id);
Label labelCacheHit = demoService.findByUuid(id);
Mockito.verify(labelRepository, Mockito.times(1)).findById(id);
}
}
With this service class code:
#Service
#RequiredArgsConstructor
#EnableCaching
public class DemoServiceImpl {
public static final String CACHE_NAME = "demoCache";
private final LabelRepository labelRepository;
#Cacheable(value = CACHE_NAME)
public Label findByUuid(UUID uuid) {
return labelRepository.findById(uuid)
.orElseThrow(() -> new EntityNotFoundException("Not found."));
}
}
And this CacheConfig:
#Configuration
public class CacheConfig {
#Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> builder
.withCacheConfiguration(DemoServiceImpl.CACHE_NAME,
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)));
}
#Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(60))
.disableCachingNullValues()
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()));
}
}
i've got a microservice which implements some optimization function by calling many times another microservice (the second one calculates so called target function value and the first micriservice changes paramters of this tagrget function)
It leads to necessity of writing some logic in Rest Controller layer. To be clear some simplified code will be represented below
#RestController
public class OptimizerController {
private OptimizationService service;
private RestTemplate restTemplate;
#GetMapping("/run_opt")
public DailyOptResponse doOpt(){
Data iniData = service.prepareData(null);
Result r = restTemplate.postForObject(http://calc-service/plain_calc", iniData, Result.class);
double dt = service.assessResult(r);
while(dt > 0.1){
Data newData = service.preapreData(r);
r = restTemplate.postForObject(http://calc-service/plain_calc", newData , Result.class);
dt = service.assessResult(r);
}
return service.prepareResponce(r);
}
As i saw in examples all people are striving to keep rest controller as simple as possible and move all logic to service layer. But what if i have to call some other microservices from service layer? Should i keep logic of data formin in service layer and return it to controller layer, use RestTemplate object in service layer or something else?
Thank you for your help
It is straightforward.
The whole logic is in the service layer (including other services).
Simple example:
Controller:
#RestController
#RequestMapping("/api/users")
public class UserController {
private final UserManager userManager;
#Autowired
public UserController(UserManager userManager) {
super();
this.userManager = userManager;
}
#GetMapping()
public List<UserResource> getUsers() {
return userManager.getUsers();
}
#GetMapping("/{userId}")
public UserResource getUser(#PathVariable Integer userId) {
return userManager.getUser(userId);
}
#PutMapping
public void updateUser(#RequestBody UserResource resource) {
userManager.updateUser(resource);
}
}
Service:
#Service
public class UserManager {
private static final Logger log = LoggerFactory.getLogger(UserManager.class);
private final UserRepository userRepository;
private final UserResourceAssembler userResourceAssembler;
private final PictureManager pictureManager;
#Autowired
public UserManager(
UserRepository userRepository,
UserResourceAssembler userResourceAssembler,
PictureManager pictureManager
) {
super();
this.userRepository = userRepository;
this.userResourceAssembler = userResourceAssembler;
this.pictureManager= pictureManager;
}
public UserResource getUser(Integer userId) {
User user = userRepository.findById(userId).orElseThrow(() -> new NotFoundException("User with ID " + userId + " not found!"));
return userResourceAssembler.toResource(user);
}
public List<UserResource> getUsers() {
return userResourceAssembler.toResources(userRepository.findAll());
}
public void updateUser(UserResource resource) {
User user = userRepository.findById(resource.getId()).orElseThrow(() -> new NotFoundException("User with ID " + resource.getId() + " not found!"));
PictureResource pictureResource = pictureManager.savePicture(user);
user = userResourceAssembler.fromResource(user, resource);
user = userRepository.save(user);
log.debug("User {} updated.", user);
}
}
Service 2:
#Service
public class PictureManager {
private static final Logger log = LoggerFactory.getLogger(PictureManager.class);
private final RestTemplate restTemplate;
#Autowired
public PictureManager(RestTemplate restTemplate) {
super();
this.restTemplate = restTemplate;
}
public PictureResource savePicture(User user) {
//do some logic with user
ResponseEntity<PictureResource> response = restTemplate.exchange(
"url",
HttpMethod.POST,
requestEntity,
PictureResource.class);
return response.getBody();
}
}
Repository:
public interface UserRepository extends JpaRepository<User, Integer> {
User findByUsername(String username);
}
I have class
public class CloneUserService {
private final UserRepository userRepository;
private final PersonRepository personRepository;
private final OrderRepository orderRepository;
public CloneUserService(UserRepository userRepository, PersonRepository personRepository, OrderRepository orderRepository) {
this.userRepository = userRepository;
this.personRepository = personRepository;
this.orderRepository = orderRepository;
}
public void createFromTemplate(String templateUserId) {
User templateUser = userRepository.getUserById(templateUserId);
Person templatePerson = personRepository.getPersonByUserId(templateUserId);
List<Order> templateOrders = orderRepository.getOrdersByUserId(templateUserId);
User newUser = cloneUserFromTemplate(templateUser);
newUser.setId("newId");
userRepository.save(newUser);
Person newPerson = clonePersonFromTemplate(templatePerson);
newPerson.setUser(newUser);
newPerson.setId("newId");
personRepository.save(newPerson);
for (Order templateOrder : templateOrders) {
Order newOrder = cloneOrderFromTemplate(templateOrder);
newOrder.setId("newId");
newOrder.setUSer(newUser);
orderRepository.save(newOrder);
}
}
private Order cloneOrderFromTemplate(Order templateOrder) {
//logic
return null;
}
private Person clonePersonFromTemplate(Person templatePerson) {
//logic
return null;
}
private User cloneUserFromTemplate(User templateUser) {
//logic
return null;
}
}
I need to test this method createFromTemplate.
I create this test. I create stabs for each repository and store saved object into this stub. And I add the additional method for getting this object for the assertion.
It works. But I have 2 problems:
My template object is mutable. It is not a big problem but it is a fact.
If I add new methods to repository interface I must implement it in stubs.
Mu question - How can I test cloned objects like theses from my example?
I don't use spring and H2DB or another in-memory database.
I have a MongoDB database.
If I use mockito I will not understand how to assert new objects in void method.
class CloneUserServiceTest {
private CloneUserService cloneUserService;
private UserRepositoryStub userRepository;
private PersonRepositoryStub personRepository;
private OrderRepositoryStub orderRepository;
#Before
public void setUp() {
User templateUser = new User();
Person templatePerson = new Person();
List<Order> templateOrders = Collections.singletonList(new Order());
userRepository = new UserRepositoryStub(templateUser);
personRepository = new PersonRepositoryStub(templatePerson);
orderRepository = new OrderRepositoryStub(templateOrders);
cloneUserService = new CloneUserService(userRepository, personRepository, orderRepository);
}
#Test
void createFromTemplate() {
cloneUserService.createFromTemplate("templateUserId");
User newUser = userRepository.getNewUser();
// assert newUser
Person newPerson = personRepository.getNewPerson();
// assert newPerson
Order newOrder = orderRepository.getNewOrder();
// assert newOrder
}
private static class UserRepositoryStub implements UserRepository {
private User templateUser;
private User newUser;
public UserRepositoryStub(User templateUser) {
this.templateUser = templateUser;
}
public User getUserById(String templateUserId) {
return templateUser;
}
public void save(User newUser) {
this.newUser = newUser;
}
public User getNewUser() {
return newUser;
}
}
private static class PersonRepositoryStub implements PersonRepository {
private Person templatePerson;
private Person newPerson;
public PersonRepositoryStub(Person templatePerson) {
this.templatePerson = templatePerson;
}
public Person getPersonByUserId(String templateUserId) {
return templatePerson;
}
public void save(Person newPerson) {
this.newPerson = newPerson;
}
public Person getNewPerson() {
return newPerson;
}
}
private static class OrderRepositoryStub implements OrderRepository {
private List<Order> templateOrders;
private Order newOrder;
public OrderRepositoryStub(List<Order> templateOrders) {
this.templateOrders = templateOrders;
}
public List<Order> getOrdersByUserId(String templateUserId) {
return templateOrders;
}
public void save(Order newOrder) {
this.newOrder = newOrder;
}
public Order getNewOrder() {
return newOrder;
}
}
}
In your scenario I would consider using mocking framework like Mockito.
Some main advantages:
Adding new methods to repository interface doesn't require implementing it in stubs
Supports exact-number-of-times and at-least-once verification
Allows flexible verification in order (e.g: verify in order what you want, not every single interaction)
Very nice and simple annotation syntax - #Mock, #InjectMocks, #Spy
Here is an example - maybe it will interest you:
// arrange
Warehouse mock = Mockito.mock(Warehouse.class);
//act
Order order = new Order(TALISKER, 50);
order.fill(warehouse); // fill will call remove() implicitly
// assert
Mockito.verify(warehouse).remove(TALISKER, 50); // verify that remove() method was actually called
I want to write a test to test my WebSocketHandlerDecorator, but I encouter some problems.
here is the error:
java.lang.IllegalArgumentException: Delegate must not be null
at org.springframework.util.Assert.notNull(Assert.java:193) at
org.springframework.web.socket.handler.WebSocketHandlerDecorator.(WebSocketHandlerDecorator.java:42)
import org.springframework.util.Assert;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
public class MyWebSocketHandlerDecorator extends WebSocketHandlerDecorator {
public MySocketHandlerDecorator(WebSocketHandler delegate) {
super(delegate);
}
#Override
public void handleMessage(final WebSocketSession session, final WebSocketMessage<?> message) throws Exception {
final TextMessage MyMessage = (TextMessage) message;
super.handleMessage(session, MyMessage);
}
}
here is my test case:
public class MyWebSocketHandlerDecpratorTest {
#Mock
private WebSocketSession session;
#Mock
WebSocketHandler delegate;
#Spy
private WebSocketHandlerDecorator webSocketHandlerDecorator = new WebSocketHandlerDecorator(delegate);
#InjectMocks
MyWebSocketHandlerDecorator myWebSocketHandlerDecorator;
private TextMessage message;
#Before
public void set_up(){
MockitoAnnotations.initMocks(this);
message = new TextMessage("Test Message".getBytes());
}
#Test
public void handleMessage()throws Exception{
myWebSocketHandlerDecorator.handleMessage(session, message);
verify(webSocketHandlerDecorator, times(1)).handleMessage(session, message);
}
}
Can anyone help me correct my test and figure out what issue it has?
public WebSocketHandlerDecorator(WebSocketHandler delegate) {
Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate;
}
You have to setup your #Spy in the #Before method, because at the time the class is created, the mocks are not yet initialized:
public class MyWebSocketHandlerDecpratorTest {
#Mock
private WebSocketSession session;
#Mock
WebSocketHandler delegate;
private WebSocketHandlerDecorator webSocketHandlerDecorator;
#InjectMocks
MyWebSocketHandlerDecorator myWebSocketHandlerDecorator;
private TextMessage message;
#Before
public void set_up(){
MockitoAnnotations.initMocks(this);
webSocketHandlerDecorator = Mockito.spy(new WebSocketHandlerDecorator(delegate));
message = new TextMessage("Test Message".getBytes());
}
#Test
public void handleMessage()throws Exception{
myWebSocketHandlerDecorator.handleMessage(session, message);
verify(webSocketHandlerDecorator, times(1)).handleMessage(session, message);
}
}