mardi 12 mars 2019

best approach to call 2 services methods from scheduler in one transaction

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?

Aucun commentaire:

Enregistrer un commentaire