DevLog ๐Ÿ˜ถ

[Kotlin & Spring] Amazon S3 ์—…๋กœ๋“œ - ๊ณตํ†ต ์—๋Ÿฌ ์ฒ˜๋ฆฌ ํ•ธ๋“ค๋ง, runCatching ๋ณธ๋ฌธ

๊ฐœ๋ฐœ์ผ์ง€

[Kotlin & Spring] Amazon S3 ์—…๋กœ๋“œ - ๊ณตํ†ต ์—๋Ÿฌ ์ฒ˜๋ฆฌ ํ•ธ๋“ค๋ง, runCatching

dolmeng2 2023. 3. 30. 23:41

์ •๋ง ์ž‘์€ ์—ญํ• ์ด์ง€๋งŒ ์กฐ๊ธˆ์”ฉ ์‚ฌ์ด๋“œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ๋Š”๋ฐ, ์ฝ”ํ‹€๋ฆฐ ๋ฌธ๋ฒ•์„ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ์ƒˆ๋กœ์šด ์ ์„ ์•Œ๊ฒŒ ๋˜์–ด ๊ธฐ๋กํ•˜๊ณ ์ž ํ•œ๋‹ค! (์ฝ”ํ‹€๋ฆฐ... ์ต์ˆ™ํ•ด์ง€๋ฉด ์ •๋ง ํŽธํ•  ๊ฒƒ ๊ฐ™์ง€๋งŒ ์•„์ง์€ ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค. ์–ด๋ ต๋‹ค!)

 


โœ”๏ธ Amazon S3 with Kotlin

์ฝ”ํ‹€๋ฆฐ๊ณผ s3๋ฅผ ์—ฐ๋™ํ•˜๊ฒŒ ๋˜๋ฉด, ์˜ˆ๊ธฐ์น˜ ๋ชปํ•œ ์„œ๋ฒ„ ์˜ค๋ฅ˜์— ๋Œ€๋น„ํ•˜์—ฌ ํŒŒ์ผ์„ ์‚ฝ์ž…ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•  ๋•Œ, ํ˜น์€ url ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ฌ ๋•Œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด Exception์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

 

ํ•œ ๊ฐ€์ง€ ๊ถ๊ธˆํ•œ ์ ์€, AmazonServiceException์˜ ๊ฒฝ์šฐ ๋ถ€๋ชจ ํƒ€์ž…์ด SdkClientException์ธ๋ฐ ์™œ ๊ตฌ๋ถ„ํ•ด๋‘์—ˆ์„๊นŒ...

 

์•„๋ฌดํŠผ, ๊ธฐ์กด์—๋Š” ์„œ๋ฒ„ ์—๋Ÿฌ์— ๋Œ€ํ•ด ์ „ํ˜€ ๊ณ ๋ คํ•˜์ง€ ์•Š์€ ์ƒํƒœ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์˜€์—ˆ๋‹ค.

fun deleteFile(fileKey: String) {
    val bucketName = s3Properties.bucketName
    s3Client.deleteObject(bucketName, fileKey)
}

์™ธ๋ถ€ api๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋งŒํผ, ๋‹น์—ฐํ•˜๊ฒŒ๋„ ์™ธ๋ถ€ ์˜ค๋ฅ˜์— ๋Œ€ํ•ด์„œ ์ฒ˜๋ฆฌ๋ฅผ ํ–ˆ์–ด์•ผ ํ–ˆ๋Š”๋ฐ ์ด๋ฅผ ๊ฐ„๊ณผํ•ด๋ฒ„๋ ธ๋‹ค ๐Ÿฅฒ

์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ํ†ตํ•ด์„œ ํ•ด๋‹น ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ ์ง€์ ์„ ๋ฐ›์•˜๊ณ , ๊ทธ๋ž˜์„œ ์ฒ˜์Œ์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์˜€๋‹ค.

class S3Exception(cause: Throwable? = null) :
        PureureumException(cause = cause, errorCode = ErrorCode.S3_UPLOAD_FAILED)
fun deleteFile(fileKey: String) {
    val bucketName = s3Properties.bucketName
    try {
        s3Client.deleteObject(bucketName, fileKey)
    } catch (e: SdkClientException) {
        throw S3Exception(e)
    }
}

S3Exception์€ ์ž์ฒด์ ์œผ๋กœ ์ •์˜ํ•œ Exception์ด๋‹ค. 

ํ•˜์ง€๋งŒ, ๋„ˆ๋ฌด ์ž๋ฐ”์Šค๋Ÿฝ๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ ๊ฒƒ์ด ์•„๋‹๊นŒ? ๋ผ๋Š” ์˜๋ฌธ์ด ๋“ค์–ด์„œ ์ด๊ฒƒ์ €๊ฒƒ ์ฐพ์•„๋ณด์•˜๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์–ป์–ด๋‚ธ ๊ฒƒ์ด, ์ฝ”ํ‹€๋ฆฐ์—๋Š” runCatching์ด๋ผ๋Š” ๊ฒŒ ์กด์žฌํ–ˆ์—ˆ๋‹ค! ๐Ÿ˜ฒ

 


โœ”๏ธ runCatching

@InlineOnly
@SinceKotlin("1.3")
public inline fun <T, R> T.runCatching(block: T.() -> R): Result<R> {
    return try {
    	Result.success(block())
    } catch (e: Throwable) {
    	Result.failure(e)
    }
}
Calls the specified function block with this value as its receiver and returns its encapsulated result if invocation was successful, catching any Throwable exception that was thrown from the block function execution and encapsulating it as a failure.

ํ•จ์ˆ˜๋ธ”๋ก์„ receiver์—๊ฒŒ ํ˜ธ์ถœํ•˜๊ณ , ํ˜ธ์ถœ์ด ์„ฑ๊ณตํ•˜๋ฉด ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์„ฑ๊ณตํ•˜๋ฉด ์„ฑ๊ณต์— ๋Œ€ํ•ด์„œ ์บก์Šํ™”๋ฅผ ์ง„ํ–‰ํ•˜๊ณ , ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์‹คํŒจ๋กœ ์บก์Šํ™”๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ 'Result'๋ผ๋Š” ์นœ๊ตฌ๊ฐ€ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. (์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ Result.failure, ์•„๋‹ˆ๋ผ๋ฉด Result.success)

@SinceKotlin("1.3")
@JvmInline
public value class Result<out T> @PublishedApi internal constructor(
@PublishedApi
    internal val value: Any?
            ) : Serializable {
            // discovery
            }

public companion object {
    /**
     * Returns an instance that encapsulates the given [value] as successful value.
     */
    @Suppress("INAPPLICABLE_JVM_NAME")
    @InlineOnly
    @JvmName("success")
    public inline fun <T> success(value: T): Result<T> =
            Result(value)

    /**
     * Returns an instance that encapsulates the given [Throwable] [exception] as failure.
     */
    @Suppress("INAPPLICABLE_JVM_NAME")
    @InlineOnly
    @JvmName("failure")
    public inline fun <T> failure(exception: Throwable): Result<T> =
            Result(createFailure(exception))
}

์ฝ”๋“œ ๋‚ด๋ถ€๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด, ์„ฑ๊ณต์ผ ๊ฒฝ์šฐ T ํƒ€์ž…์˜ ๊ฐ’์„ ๊ฐ€์ง€๊ฒŒ ๋˜๊ณ , ์‹คํŒจ์ผ ๊ฒฝ์šฐ exception์„ ๊ฐ’์œผ๋กœ ๊ฐ€์ง€๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.


โœ”๏ธ Result๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ญ๊ฐ€ ์ข‹์€๋ฐ?

Result๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ๊ฝค๋‚˜ ๋‹ค์–‘ํ•œ ๋ฉ”์„œ๋“œ๋“ค์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ๋‹ค.

 

๐Ÿ’ฌ getOrNull

@InlineOnly
public inline fun getOrNull(): T? =
        when {
            isFailure -> null
            else -> value as T
        }

์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํ•ด๋‹น ์—๋Ÿฌ๋ฅผ ๋ฌด์‹œํ•˜๊ณ , null์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ’ฌ getOrDefault

@InlineOnly
@SinceKotlin("1.3")
public inline fun <R, T : R> Result<T>.getOrDefault(defaultValue: R): R {
        if (isFailure) return defaultValue
        return value as T
}

์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ง€์ •ํ•œ ๊ธฐ๋ณธ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์„ค์ •ํ•œ๋‹ค.

 

๐Ÿ’ฌ getOrElse

@InlineOnly
@SinceKotlin("1.3")
public inline fun <R, T : R> Result<T>.getOrElse(onFailure: (exception: Throwable) -> R): R {
    contract {
    	callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE)
    }
    return when (val exception = exceptionOrNull()) {
        null -> value as T
        else -> onFailure(exception)
    }
}

์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํŠน์ •ํ•œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

์—ฌ๊ธฐ์„œ ๋‘ ๊ฐœ์˜ ์ธ์ž๋ฅผ ๋ฐ›๋Š”๋ฐ, ์ฒซ ๋ฒˆ์งธ๋Š” ๊ฐ์ฒด๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ์„ ๋•Œ ํ•ด๋‹น ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋žŒ๋‹ค ํ•จ์ˆ˜, ๋‘ ๋ฒˆ์งธ๋Š” ๊ฐ์ฒด๊ฐ€ ์‹คํŒจํ–ˆ์„ ๊ฒฝ์šฐ Throwable ๊ฐ์ฒด๋ฅผ ์ฒ˜๋ฆฌํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•  ๋Œ€์ฒด ๊ฐ’์„ ์ƒ์„ฑํ•˜๋Š” ๋žŒ๋‹ค ํ•จ์ˆ˜์ด๋‹ค.

 

์—ฌ๊ธฐ์„œ ๋‚ด๋ถ€์ ์œผ๋กœ contract ๋ธ”๋ก์„ ํ™œ์šฉํ•˜๋ฉด (์ธ๋ผ์ธ ํ•จ์ˆ˜์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ) ํ•จ์ˆ˜ ํ˜ธ์ถœ์˜ ์ „์ œ ์กฐ๊ฑด, ํ˜น์€ ์‚ฌํ›„ ์กฐ๊ฑด ๋“ฑ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์—ฌ๊ธฐ์„œ๋Š” callInPlace ํ•จ์ˆ˜๋ฅผ ํ™œ์šฉํ•˜์—ฌ onFailure ํ•จ์ˆ˜๊ฐ€ ์ตœ๋Œ€ 1๋ฒˆ๋งŒ ํ˜ธ์ถœ๋˜๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•œ๋‹ค. 

๊ทธ๋ฆฌ๊ณ , when์ ˆ์„ ํ†ตํ•ด์„œ ๊ฐ์ฒด๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜, ์‹คํŒจํ–ˆ์„ ๊ฒฝ์šฐ ๋Œ€์ฒด ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋œ๋‹ค. (exceptionOrNull ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์‹คํŒจํ•œ ๊ฒฝ์šฐ Throwable์„, ๊ทธ๋ฆฌ๊ณ  onFailure๋ฅผ ํ†ตํ•ด ๋Œ€์ฒด๊ฐ’์„ ์ƒ์„ฑํ•˜๊ฒŒ ๋œ๋‹ค.)

 

๐Ÿซ  ์ธ๋ผ์ธ ํ•จ์ˆ˜

// ์ผ๋ฐ˜ ํ•จ์ˆ˜
fun add(a: Int, b: Int): Int {
    return a + b
}

// ์ธ๋ผ์ธ ํ•จ์ˆ˜
inline fun add(a: Int, b: Int): Int {
	return a + b
}

์ธ๋ผ์ธ ํ•จ์ˆ˜๋ผ๋Š” ๊ฒƒ์„ ์ฒ˜์Œ ๋“ค์–ด๋ด์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ž‘์„ฑํ•ด๋ณด๊ณ ์ž ํ•œ๋‹ค.

์ธ๋ผ์ธ ํ•จ์ˆ˜๋Š” ํ˜ธ์ถœ๋˜๋Š” ์œ„์น˜์— ํ•จ์ˆ˜์˜ ๋‚ด์šฉ์„ ๋ณต์‚ฌํ•˜์—ฌ ํ•จ์ˆ˜ ํ˜ธ์ถœ ๋น„์šฉ์„ ์ค„์ด๋Š”๋ฐ, inline ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ์„ ์–ธํ•œ๋‹ค.

์ผ๋ฐ˜ ํ•จ์ˆ˜๋Š” ํ˜ธ์ถœํ•  ๋•Œ๋งˆ๋‹ค ํ•ด๋‹น ํ•จ์ˆ˜์— ๋Œ€ํ•œ ์Šคํƒ ํ”„๋ ˆ์ž„์— ์ƒ์„ฑ๋˜๊ณ , ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰์ด ๋˜๊ณ , ๊ฒฐ๊ณผ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ ๋’ค ์Šค์ฝ”ํ”„๊ฐ€ ๋๋‚˜๋ฉด ์Šคํƒ ํ”„๋ ˆ์ž„์ด ์ œ๊ฑฐ๋œ๋‹ค. ํ•˜์ง€๋งŒ, ์ธ๋ผ์ธ ํ•จ์ˆ˜๋Š” ํ˜ธ์ถœ ์‹œ์ ์— ํ˜ธ์ถœ๋ถ€์—์„œ ํ•จ์ˆ˜๊ฐ€ ์ง์ ‘ ์‚ฝ์ž…๋˜๊ธฐ ๋•Œ๋ฌธ์—, ํ•จ์ˆ˜ ํ˜ธ์ถœ์˜ ์˜ค๋ฒ„ ํ—ค๋“œ๋ฅผ ์ค„์ด๊ฒŒ ๋œ๋‹ค.

 

๐Ÿ’ฌ exceptionOrNull

public fun exceptionOrNull(): Throwable? =
        when (value) {
            is Failure -> value.exception
            else -> null
        }

์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ์—๋งŒ ํ•ด๋‹น ์—๋Ÿฌ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ , ์•„๋‹ˆ๋ผ๋ฉด null์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 


โœ”๏ธ runCatching ์ ์šฉํ•˜๊ธฐ

์•„๋ฌดํŠผ, ์œ„์— ๋‚˜์˜จ ๋‹ค์–‘ํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋‚ด ์ฝ”๋“œ์— ์ ์šฉํ•ด๋ณด์•˜๋‹ค.

fun deleteFile(fileKey: String) {
    val bucketName = s3Properties.bucketName
    return runCatching {
        	s3Client.deleteObject(bucketName, fileKey)
        }.getOrElse {
        	throw S3Exception(it)
        }
}

ํ™•์‹คํžˆ ์ด์ „๋ณด๋‹ค ์ฝ”ํ‹€๋ฆฐ์Šค๋Ÿฝ๊ฒŒ ์ž‘์„ฑํ•œ ๊ฒƒ ๊ฐ™์•„์„œ ์ข‹์•˜๋‹ค. 

ํ•˜์ง€๋งŒ, ๋ฌธ์ œ์ ์ด ์žˆ์—ˆ๋‹ค. ํŒŒ์ผ ์‚ญ์ œ, ์ƒ์„ฑ, ์กฐํšŒ ์‹œ์—๋„ ๋ชจ๋‘ runCatching ๋ธ”๋ก์ด ๋“ค์–ด๊ฐ€๋‹ค ๋ณด๋‹ˆ ์ค‘๋ณต์ด ๋˜๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.

 

๊ทธ๋ž˜์„œ, ํ•จ์ˆ˜ํ˜• ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ™œ์šฉํ•ด๋ณด๊ณ ์ž ํ–ˆ๋‹ค.

private fun <T> execute(operation: () -> T): T {
        return runCatching { operation() }
        .getOrElse { throw S3Exception(it) }
}

๋ณ„๊ฑฐ ์—†๋‹ค. ๊ทธ๋ƒฅ ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ๋กœ์ง์— ๋Œ€ํ•ด์„œ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋žŒ๋‹ค๋กœ ๋„˜๊ฒจ์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

 

์œ„์˜ ํ˜ธ์ถœ๋ถ€๋„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ค„์ผ ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

override fun deleteFile(fileKey: String) {
    val bucketName = s3Properties.bucketName
    return execute {
    	s3Client.deleteObject(bucketName, fileKey)
    }
}

๋•๋ถ„์— s3 ๊ด€๋ จ ์—๋Ÿฌ ๋กœ์ง๋“ค์€ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค ๐Ÿ˜Š

 


 

์–ผ๋ฅธ ์ฝ”ํ‹€๋ฆฐ ์ž˜ํ•˜๊ณ  ์‹ถ๋‹ค... ์•„์ง ๋„ˆ๋ฌด ์–ด๋ ต๋‹ค ๐Ÿฅฒ

์Šคํ”„๋ง๋„, JPA๋„ ๋‹ค ๋‹ค์‹œ ๊ณต๋ถ€ํ•ด์•ผ๊ฒ ๋‹ค... ๋‹ค ๊นŒ๋จน์—ˆ๋‹ค... ใ…Ž ํž˜๋‚ด์ž...

Comments