Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore: πŸ”§ Seperate Business Logic From DeviceTokenRegisterService #210

Merged
merged 13 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package kr.co.pennyway.domain.context.account.collection;

import kr.co.pennyway.domain.domains.device.domain.DeviceToken;
import kr.co.pennyway.domain.domains.device.exception.DeviceTokenErrorCode;
import kr.co.pennyway.domain.domains.device.exception.DeviceTokenErrorException;
import kr.co.pennyway.domain.domains.user.domain.User;
import kr.co.pennyway.domain.domains.user.exception.UserErrorCode;
import kr.co.pennyway.domain.domains.user.exception.UserErrorException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;

import java.util.List;

@Slf4j
public class DeviceTokenRegisterCollection {
private final List<DeviceToken> userDeviceTokens;
private DeviceToken deviceToken;

public DeviceTokenRegisterCollection() {
this.deviceToken = null;
this.userDeviceTokens = List.of();
}

public DeviceTokenRegisterCollection(DeviceToken deviceToken) {
this.deviceToken = deviceToken;
this.userDeviceTokens = List.of();
}

/**
* @param userDeviceTokens :
*/
public DeviceTokenRegisterCollection(DeviceToken deviceToken, List<DeviceToken> userDeviceTokens) {
this.deviceToken = deviceToken;
this.userDeviceTokens = userDeviceTokens;
}

/**
* μ‚¬μš©μžμ˜ λ””λ°”μ΄μŠ€ 토큰을 μƒμ„±ν•˜κ±°λ‚˜ κ°±μ‹ ν•œλ‹€.
*
* <pre>
* [λΉ„μ¦ˆλ‹ˆμŠ€ κ·œμΉ™]
* - 같은 {userId, deviceId}에 λŒ€ν•΄ μƒˆλ‘œμš΄ 토큰이 λ°œκΈ‰λ  수 μžˆμ§€λ§Œ, ν™œμ„±ν™”λœ 토큰은 ν•˜λ‚˜μ—¬μ•Ό ν•©λ‹ˆλ‹€.
* - {deviceId, token} 쑰합은 μ‹œμŠ€ν…œ μ „μ²΄μ—μ„œ μœ μΌν•΄μ•Ό ν•©λ‹ˆλ‹€.
* - device token이 이미 λ“±λ‘λœ 경우, μ†Œμœ μž 정보λ₯Ό κ°±μ‹ ν•˜κ³  λ§ˆμ§€λ§‰ 둜그인 μ‹œκ°„μ„ κ°±μ‹ ν•œλ‹€.
* - device token이 λ“±λ‘λ˜μ§€ μ•Šμ€ 경우, μƒˆλ‘œμš΄ device token을 μƒμ„±ν•œλ‹€.
* </pre>
*
* @param owner User : μ‚¬μš©μž 정보
* @param deviceId String: λ””λ°”μ΄μŠ€ μ‹λ³„μž
* @param deviceName String: λ””λ°”μ΄μŠ€ 이름
* @param token String: λ””λ°”μ΄μŠ€ 토큰
* @return {@link DeviceToken} μ‚¬μš©μžμ˜ 기기둜 λ“±λ‘λœ Device 정보
* @throws UserErrorException {@link UserErrorCode#NOT_FOUND} : μ‚¬μš©μž νŒŒλΌλ―Έν„°κ°€ null인 경우
* @throws DeviceTokenErrorException {@link DeviceTokenErrorCode#DUPLICATED_DEVICE_TOKEN} : 이미 λ“±λ‘λœ ν™œμ„±ν™” λ””λ°”μ΄μŠ€ ν† ν°μ˜ deviceId와 λ‹€λ₯Έ deviceIdκ°€ λ“€μ–΄μ˜¨ 경우
*/
public DeviceToken register(User owner, @NonNull String deviceId, @NonNull String deviceName, @NonNull String token) {
if (owner == null) {
log.error("λ””λ°”μ΄μŠ€ 토큰을 등둝할 μ‚¬μš©μž 정보가 μ—†μŠ΅λ‹ˆλ‹€.");
throw new UserErrorException(UserErrorCode.NOT_FOUND);
}

DeviceToken existingDeviceToken = this.getDeviceTokenByToken(token);

return (existingDeviceToken != null)
? this.updateDevice(owner, deviceId, existingDeviceToken)
: this.createDevice(owner, deviceId, deviceName, token);
}

private DeviceToken getDeviceTokenByToken(String token) {
if (this.deviceToken != null && this.deviceToken.getToken().equals(token)) {
return this.deviceToken;
}

return null;
}

private DeviceToken updateDevice(User user, String deviceId, DeviceToken originalDeviceToken) {
if (isDuplicatedDeviceToken(deviceId, originalDeviceToken)) {
log.error("ν™œμ„±ν™”λœ 토큰을 λ‹€λ₯Έ λ””λ°”μ΄μŠ€μ—μ„œ μ‚¬μš© μ€‘μž…λ‹ˆλ‹€.");
throw new DeviceTokenErrorException(DeviceTokenErrorCode.DUPLICATED_DEVICE_TOKEN);
}

originalDeviceToken.handleOwner(user, deviceId);
log.info("λ””λ°”μ΄μŠ€ 토큰이 κ°±μ‹ λ˜μ—ˆμŠ΅λ‹ˆλ‹€. deviceId: {}, token: {}", deviceId, originalDeviceToken.getToken());

return originalDeviceToken;
}

private boolean isDuplicatedDeviceToken(String deviceId, DeviceToken originalDeviceToken) {
return !originalDeviceToken.getDeviceId().equals(deviceId) && originalDeviceToken.isActivated();
}

private DeviceToken createDevice(User user, String deviceId, String deviceName, String token) {
this.deviceToken = DeviceToken.of(token, deviceId, deviceName, user);
log.info("μƒˆλ‘œμš΄ λ””λ°”μ΄μŠ€ 토큰이 μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€. deviceId: {}, token: {}", deviceId, token);

deactivateExistingTokens();

return this.deviceToken;
}

/**
* νŠΉμ • μ‚¬μš©μžμ˜ λ””λ°”μ΄μŠ€μ— λŒ€ν•œ κΈ°μ‘΄ ν™œμ„± 토큰듀을 λΉ„ν™œμ„±ν™”ν•©λ‹ˆλ‹€.
* μƒˆλ‘œμš΄ 토큰 등둝 μ‹œ ν˜ΈμΆœλ˜μ–΄ ν•˜λ‚˜μ˜ λ””λ°”μ΄μŠ€μ— ν•˜λ‚˜μ˜ ν™œμ„± ν† ν°λ§Œ μ‘΄μž¬ν•˜λ„λ‘ 보μž₯ν•©λ‹ˆλ‹€.
*/
private void deactivateExistingTokens() {
userDeviceTokens.stream()
.filter(token -> token.getDeviceId().equals(deviceToken.getDeviceId()))
.filter(DeviceToken::isActivated)
.forEach(DeviceToken::deactivate);
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
package kr.co.pennyway.domain.context.account.service;

import kr.co.pennyway.common.annotation.DomainService;
import kr.co.pennyway.domain.context.account.collection.DeviceTokenRegisterCollection;
import kr.co.pennyway.domain.domains.device.domain.DeviceToken;
import kr.co.pennyway.domain.domains.device.exception.DeviceTokenErrorCode;
import kr.co.pennyway.domain.domains.device.exception.DeviceTokenErrorException;
import kr.co.pennyway.domain.domains.device.service.DeviceTokenRdbService;
import kr.co.pennyway.domain.domains.user.domain.User;
import kr.co.pennyway.domain.domains.user.exception.UserErrorCode;
import kr.co.pennyway.domain.domains.user.exception.UserErrorException;
import kr.co.pennyway.domain.domains.user.service.UserRdbService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Slf4j
@DomainService
Expand All @@ -25,14 +23,6 @@ public class DeviceTokenRegisterService {
/**
* μ‚¬μš©μžμ˜ λ””λ°”μ΄μŠ€ 토큰을 μƒμ„±ν•˜κ±°λ‚˜ κ°±μ‹ ν•œλ‹€.
*
* <pre>
* [λΉ„μ¦ˆλ‹ˆμŠ€ κ·œμΉ™]
* - 같은 {userId, deviceId}에 λŒ€ν•΄ μƒˆλ‘œμš΄ 토큰이 λ°œκΈ‰λ  수 μžˆμ§€λ§Œ, ν™œμ„±ν™”λœ 토큰은 ν•˜λ‚˜μ—¬μ•Ό ν•©λ‹ˆλ‹€.
* - {deviceId, token} 쑰합은 μ‹œμŠ€ν…œ μ „μ²΄μ—μ„œ μœ μΌν•΄μ•Ό ν•©λ‹ˆλ‹€.
* - device token이 이미 λ“±λ‘λœ 경우, μ†Œμœ μž 정보λ₯Ό κ°±μ‹ ν•˜κ³  λ§ˆμ§€λ§‰ 둜그인 μ‹œκ°„μ„ κ°±μ‹ ν•œλ‹€.
* - device token이 λ“±λ‘λ˜μ§€ μ•Šμ€ 경우, μƒˆλ‘œμš΄ device token을 μƒμ„±ν•œλ‹€.
* </pre>
*
* @param userId μ‚¬μš©μž μ‹λ³„μž
* @param deviceId λ””λ°”μ΄μŠ€ μ‹λ³„μž
* @param deviceName λ””λ°”μ΄μŠ€ 이름
Expand All @@ -41,47 +31,15 @@ public class DeviceTokenRegisterService {
*/
@Transactional
public DeviceToken execute(Long userId, String deviceId, String deviceName, String deviceToken) {
User user = userRdbService.readUser(userId).orElseThrow(() -> new UserErrorException(UserErrorCode.NOT_FOUND));

return getOrCreateDevice(user, deviceId, deviceName, deviceToken);
}

/**
* μ‚¬μš©μžμ˜ λ””λ°”μ΄μŠ€ 토큰을 μƒμ„±ν•©λ‹ˆλ‹€.
* λ§Œμ•½, 이미 λ“±λ‘λœ λ””λ°”μ΄μŠ€ 토큰이 μ‘΄μž¬ν•œλ‹€λ©΄, ν•΄λ‹Ή 토큰을 κ°±μ‹ ν•˜κ³  λ°˜ν™˜ν•©λ‹ˆλ‹€.
*/
private DeviceToken getOrCreateDevice(User user, String deviceId, String deviceName, String deviceToken) {
return deviceTokenRdbService.readDeviceByToken(deviceToken)
.map(originalDeviceToken -> updateDevice(user, deviceId, originalDeviceToken))
.orElseGet(() -> createDevice(user, deviceId, deviceName, deviceToken));
}

private DeviceToken updateDevice(User user, String deviceId, DeviceToken originalDeviceToken) {
if (!originalDeviceToken.getDeviceId().equals(deviceId) && originalDeviceToken.isActivated()) {
throw new DeviceTokenErrorException(DeviceTokenErrorCode.DUPLICATED_DEVICE_TOKEN);
}

originalDeviceToken.handleOwner(user, deviceId);
return originalDeviceToken;
}

private DeviceToken createDevice(User user, String deviceId, String deviceName, String deviceToken) {
deactivateExistingTokens(user.getId(), deviceId);
Optional<User> user = userRdbService.readUser(userId);
Optional<DeviceToken> existingDeviceToken = deviceTokenRdbService.readDeviceByToken(deviceToken);
List<DeviceToken> userDeviceTokens = deviceTokenRdbService.readByUserIdAndDeviceId(userId, deviceId);

DeviceToken newDeviceToken = DeviceToken.of(deviceToken, deviceId, deviceName, user);
DeviceToken newDeviceToken = new DeviceTokenRegisterCollection(
existingDeviceToken.orElse(null),
userDeviceTokens
).register(user.orElse(null), deviceId, deviceName, deviceToken);

return deviceTokenRdbService.createDevice(newDeviceToken);
}

/**
* νŠΉμ • μ‚¬μš©μžμ˜ λ””λ°”μ΄μŠ€μ— λŒ€ν•œ κΈ°μ‘΄ ν™œμ„± 토큰듀을 λΉ„ν™œμ„±ν™”ν•©λ‹ˆλ‹€.
* μƒˆλ‘œμš΄ 토큰 등둝 μ‹œ ν˜ΈμΆœλ˜μ–΄ ν•˜λ‚˜μ˜ λ””λ°”μ΄μŠ€μ— ν•˜λ‚˜μ˜ ν™œμ„± ν† ν°λ§Œ μ‘΄μž¬ν•˜λ„λ‘ 보μž₯ν•©λ‹ˆλ‹€.
*/
private void deactivateExistingTokens(Long userId, String deviceId) {
List<DeviceToken> userDeviceTokens = deviceTokenRdbService.readByUserIdAndDeviceId(userId, deviceId);

userDeviceTokens.stream()
.filter(DeviceToken::isActivated)
.forEach(DeviceToken::deactivate);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import org.springframework.test.context.ActiveProfiles;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@Slf4j
Expand All @@ -53,29 +55,62 @@ void setUp() {
savedUser = userRepository.save(UserFixture.GENERAL_USER.toUser());
}

@Test
@DisplayName("μƒˆλ‘œμš΄ 토큰 등둝 μš”μ²­ μ‹œ λ””λ°”μ΄μŠ€ 토큰이 μ •μƒμ μœΌλ‘œ μ €μž₯λ©λ‹ˆλ‹€")
void when_registering_new_token_then_save_successfully() {
// given
String deviceId = "newDevice";
String token = "newToken";

// when
DeviceToken result = deviceTokenRegisterService.execute(savedUser.getId(), deviceId, "Android", token);

// then
// 1. λ°˜ν™˜λœ κ²°κ³Ό 검증
assertEquals(token, result.getToken());
assertEquals(deviceId, result.getDeviceId());
assertEquals(savedUser.getId(), result.getUser().getId());
assertTrue(result.isActivated());

// 2. μ‹€μ œ DB μ €μž₯ μ—¬λΆ€ 검증
DeviceToken savedToken = deviceTokenRepository.findById(result.getId())
.orElseThrow(() -> new IllegalStateException("μ €μž₯된 토큰을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."));

assertEquals(token, savedToken.getToken());
assertEquals(deviceId, savedToken.getDeviceId());
assertEquals(savedUser.getId(), savedToken.getUser().getId());
assertTrue(savedToken.isActivated());
}

@Test
@Transactional
@DisplayName("λ””λ°”μ΄μŠ€ 토큰 등둝 μ‹œ κΈ°μ‘΄ ν™œμ„± 토큰은 λΉ„ν™œμ„±ν™”λ©λ‹ˆλ‹€")
void shouldDeactivateExistingTokensWhenRegisteringNew() {
@DisplayName("μ‚¬μš©μžκ°€ λ™μΌν•œ λ””λ°”μ΄μŠ€μ— μƒˆλ‘œμš΄ 토큰을 λ“±λ‘ν•˜λ©΄ κΈ°μ‘΄ 토큰이 λΉ„ν™œμ„±ν™”λ©λ‹ˆλ‹€")
void when_registering_new_token_for_same_device_then_deactivate_existing_token() {
// given
String deviceId = "device1";
DeviceToken firstToken = deviceTokenRepository.save(DeviceToken.of("token1", deviceId, "Android", savedUser));

// when
DeviceToken firstToken = deviceTokenRegisterService.execute(savedUser.getId(), deviceId, "Android", "token1");
DeviceToken secondToken = deviceTokenRegisterService.execute(savedUser.getId(), deviceId, "Android", "token2");

// then
assertFalse(firstToken.isActivated());
assertTrue(secondToken.isActivated());
assertEquals(deviceId, secondToken.getDeviceId());

// DB에 μ‹€μ œλ‘œ μ €μž₯λ˜μ—ˆλŠ”μ§€ 확인
List<DeviceToken> savedTokens = deviceTokenRepository.findAllByUser_Id(savedUser.getId());
assertEquals(2, savedTokens.size());
assertEquals(1L, savedTokens.stream().filter(DeviceToken::isActivated).count(), "ν™œμ„±ν™”λœ 토큰은 1κ°œμ—¬μ•Ό ν•©λ‹ˆλ‹€");
}

@Test
@Transactional
@DisplayName("ν™œμ„±ν™”λœ 토큰이 λ‹€λ₯Έ λ””λ°”μ΄μŠ€μ—μ„œ μ‚¬μš©λ˜λ©΄ μ˜ˆμ™Έκ°€ λ°œμƒν•©λ‹ˆλ‹€")
void shouldThrowExceptionWhenActiveTokenIsUsedOnDifferentDevice() {
@DisplayName("ν™œμ„±ν™”λœ 토큰을 λ‹€λ₯Έ λ””λ°”μ΄μŠ€μ—μ„œ μ‚¬μš©ν•˜λ €κ³  ν•˜λ©΄ μ˜ˆμ™Έκ°€ λ°œμƒν•©λ‹ˆλ‹€")
void when_using_active_token_on_different_device_then_throw_exception() {
// given
String token = "token1";
deviceTokenRegisterService.execute(savedUser.getId(), "device1", "Android", token);
deviceTokenRepository.save(DeviceToken.of(token, "device1", "iPhone", savedUser));

// when & then
DeviceTokenErrorException exception = assertThrowsExactly(
Expand All @@ -86,6 +121,7 @@ void shouldThrowExceptionWhenActiveTokenIsUsedOnDifferentDevice() {
}

@Test
@Transactional
@DisplayName("같은 deviceId, token / λ‹€λ₯Έ μ‚¬μš©μž κ°±μ‹  μš”μ²­μ΄λΌλ©΄, λ””λ°”μ΄μŠ€ ν† ν°μ˜ μ†Œμœ κΆŒμ΄ λ‹€λ₯Έ μ‚¬μš©μžμ—κ²Œ μ΄μ „λ©λ‹ˆλ‹€")
void shouldTransferTokenOwnership() {
// given
Expand All @@ -94,13 +130,45 @@ void shouldTransferTokenOwnership() {
String deviceId = "device1";
String token = "token1";

DeviceToken firstUserToken = deviceTokenRepository.save(DeviceToken.of(token, deviceId, "Android", savedUser));

// when
DeviceToken firstUserToken = deviceTokenRegisterService.execute(savedUser.getId(), deviceId, "Android", token);
DeviceToken secondUserToken = deviceTokenRegisterService.execute(anotherUser.getId(), deviceId, "Android", token);

// then
assertEquals(firstUserToken.getId(), secondUserToken.getId());
assertEquals(anotherUser.getId(), secondUserToken.getUser().getId());
assertTrue(secondUserToken.isActivated());

List<DeviceToken> firstUserTokens = deviceTokenRepository.findAllByUser_Id(savedUser.getId());
List<DeviceToken> secondUserTokens = deviceTokenRepository.findAllByUser_Id(anotherUser.getId());

assertTrue(firstUserTokens.isEmpty(), "첫 번째 μ‚¬μš©μžμ˜ 토큰이 μ—†μ–΄μ•Ό ν•©λ‹ˆλ‹€");
assertEquals(1, secondUserTokens.size(), "두 번째 μ‚¬μš©μžμ˜ 토큰이 1개 μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€");
}

@Test
@DisplayName("μ‚¬μš©μžλŠ” μ—¬λŸ¬ 기기에 μ„œλ‘œ λ‹€λ₯Έ 토큰을 등둝할 수 μžˆμŠ΅λ‹ˆλ‹€")
void when_user_registers_multiple_devices_then_allow_different_tokens() {
// given
String device1Token = "token1";
String device2Token = "token2";

// when
DeviceToken result1 = deviceTokenRegisterService.execute(savedUser.getId(), "device1", "Android", device1Token);
DeviceToken result2 = deviceTokenRegisterService.execute(savedUser.getId(), "device2", "iPhone", device2Token);

// then
assertTrue(result1.isActivated());
assertTrue(result2.isActivated());
assertNotEquals(result1.getDeviceId(), result2.getDeviceId());
assertNotEquals(result1.getToken(), result2.getToken());

// DB에 μ‹€μ œλ‘œ μ €μž₯λ˜μ—ˆλŠ”μ§€ 확인
List<DeviceToken> savedTokens = deviceTokenRepository.findAllByUser_Id(savedUser.getId());
assertEquals(2, savedTokens.size());
assertTrue(savedTokens.stream().allMatch(DeviceToken::isActivated));

deviceTokenRepository.deleteAll(savedTokens);
}
}
Loading
Loading