본문 바로가기

Proj/Tripot

앱 버전 저장 및 관리하기

1. 개요

 Tripot 프로젝트는 모바일 서비스이다. 어플을 운영하다 보면 버그가 생길 수 있다. 만약 치명적인 버그라면? 이를 수정했는데 업데이트 없이 그대로 사용하는 유저가 있다면? 업데이트를 한 사용자와 하지 않은 사용자는 UX적으로 큰 차이를 경험하게 될 것이다. 이를 방지하기 위해 버전을 서버에 저장하고, 사용자의 버전에 따라 업데이트를 권장하거나 강제하는 기능을 만들고자 한다.

2. ERD

 ERD는 다음과 같다. 개인적으로 처음 구현해보는 기능이라 설계에서 애를 좀 먹었다. 필드를 하나씩 살펴보자.

  • version_id: 해당 정보의 PK이다.
  • version: 앱 버전을 기술한다. 1.0.1, 1.0.2와 같이 앱 버전이 들어갈 수 있다. 원활한 버전 비교 및 저장을 위해 문자열로 저장했다.(1.2.10 등)
  • force_update: 해당 버전의 하위 버전이 위험하다면 true를 저장한다. 이 때는 버전 비교 시 하위 버전일 경우 업데이트를 강제하기로 한다.
  • platform: enum 필드이다. 현재는 ios만으로 운영하고 있지만 추후 안드로이드 서비스를 예정함에 따라 플랫폼 별로 버전을 따로 관리하고자 추가하였다.

이를 기반으로 도메인을 다음과 같이 작성하였다.

@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Version extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "version_id")
    private Long id;

    @Column(length = 15)
    private String version;

    //true일 경우 해당 하위 버전은 해당 버전까지의 강제 업데이트를 요구함
    private Boolean forceUpdate;

    @Enumerated(value = EnumType.STRING)
    private Platform platform;

}

3. 기능

3-1. VersionService

 기능 자체는 매우 간단하므로 묶어서 작성하였다.

@Service
@RequiredArgsConstructor
@Slf4j
@Transactional(readOnly = true)
public class VersionService {

    private final VersionRepository versionRepository;

    @Transactional
    public void createVersion(VersionDto versionDto, Platform platform) {
        Version version = Version.builder()
                .platform(platform)
                .version(versionDto.version())
                .forceUpdate(versionDto.forceUpdate())
                .build();

        versionRepository.save(version);
    }

    public VersionCheckResponseDto checkVersion(Platform platform, String version) {

        Version latestVersion = versionRepository.findFirstByPlatformOrderByCreatedDateDesc(platform)
                .orElseThrow(() -> new CustomException(StatusCode.VERSION_NOT_FOUND));

        boolean forceUpdate = false;
        boolean requireUpdate = false;

        if (version.compareTo(latestVersion.getVersion()) < 0) {
            requireUpdate = true;
            if (latestVersion.getForceUpdate()) {
                forceUpdate = true;
            }
        }

        return VersionCheckResponseDto.builder()
                .requireUpdate(requireUpdate)
                .forceUpdate(forceUpdate)
                .build();


    }
}
  • createVersion: 간단하다. 컨트롤러 계층에서 넘어온 버전 정보, 강제 여부, 플랫폼을 받아 이를 DB에 저장한다.
  • checkVersion: 사용자로부터 사용 플랫폼과 버전 정보를 받아온다. DB에서는 해당 플랫폼에 알맞는 가장 최근에 저장한, 즉 최신 버전을 받아온다. 이 둘을 비교하여 사용자가 구버전일 경우, 최신 버전이 강제해야 할 경우 강제 여부를 true, 그렇지 않다면 false를 반환하여 리턴한다.

3-2. VersionController

@RestController
@RequiredArgsConstructor
@Slf4j
public class VersionController {

    private final VersionService versionService;

    @Secured("ADMIN")
    @PostMapping("/api/v1/versions/{platform}")
    public ResponseEntity<CommonResponse<Object>> createVersion(@RequestBody VersionDto versionDto, @PathVariable("platform") String platform) {
        versionService.createVersion(versionDto, Platform.valueOf(platform.toUpperCase()));
        return ResponseEntity.status(StatusCode.VERSION_CREATE_SUCCESS.getHttpCode()).body(CommonResponse.success(StatusCode.VERSION_CREATE_SUCCESS, null));
    }

    @GetMapping("/api/v1/versions/{platform}/check")
    public ResponseEntity<CommonResponse<VersionCheckResponseDto>> checkVersion(@RequestParam("version") String version, @PathVariable("platform") String platform) {
        return ResponseEntity.status(StatusCode.VERSION_CHECK_SUCCESS.getHttpCode()).body(CommonResponse.success(StatusCode.VERSION_CHECK_SUCCESS, versionService.checkVersion(Platform.valueOf(platform.toUpperCase()), version)));
    }
}

 컨트롤러 역시 간단히 작성했다. 버전을 저장하는 것은 관리자만 가능해야 하므로 Secured로 권한을 제한했다.