[Kotlin+SpringBoot] 코프링 스터디 2주차 회고, 스프링! 3-Layer Architecture?

SSO·2022년 5월 16일
2
post-thumbnail

Retrospect


2주차 회고! 오늘 벌써 4주차까지 스터디 나갔다 ㅋㅋ큐ㅠㅠ 열심히 써야지..!
2주차에서는 깊게 생각하지 않고 받아들였던 것들에 대해서 좀 더 깊게 생각해볼 수 있었다. 이번 핵심은 Service와 ServiceImpl !! 서비스단은 인턴으로 일 하면서도 많이 만들고 쓰던 파일인데 굳이 왜 필요한 지는 생각해보지 못했던 것 같다. 이번 주 스터디를 통해서 Service로 나눠야 하는 이유와 그 장점에 대해 배울 수 있었다.


MenHel Study


2번째 멘헬 스터디! ..에서는 앞으로 사용할 DB와 2주차 수업의 내용에 대해 이야기를 나누어 본 시간이었다. 먼저 의존성 주입에 대해 얼마나 알고 있냐는 질문을 받았었다. 오.. 진짜 들어만 보고 스프링 관련 개발 환경 세팅에서만 사용하던 용어였다. 정말 그 이상으로는 생각해보지도 않았다. 제대로 공부하고자 하니 중요한 용어들이 눈에 띄는 느낌 ㅎㅎ.. 개발의 길은 아직 멀었군..! 그 후 DB 사용의 방향성까지 의견을 나누어보며 2주차 멘헬 스터디는 끝!~!
스터디 후 SOPT 30기 velog의 의존성 주입 아티클을 읽어보며 대강 개념을 이해해보고자 했다. 글.. 어렵다.. 사실 100프로 이해는 못하고 2주차 수업까지 들으며 중요성 정도는 이해한 느낌 ㅎㅁㅎ??


Week2 Study


2주차 스터디도 시작! 공부하자@!


🌱 IoC (Inversion of Control) : 제어의 역전

프로그램의 제어 흐름(호출 구조)를 뒤엎는 것

말 그대로 컨트롤을 역전시키는 것이다. 어디서? 개발자에게서! 우리는 개발을 할 때 거의 모두가 프레임워크를 사용하고 있다. 왜 쓸까? ㅇㅅㅇ.. 편하니까^!^ 그러니까 프레임워크에게, 즉 프로그램에게 개발자의 권한을 넘겨서 필요한 파일을 생성하고 소멸시키며 코드 리팩토링을 수월하게 하는 것으로 이해했다. 이 특징은 다음과 같다.

  • 객체의 생성/책임 수행을 특정 객체에게 위임하여 자신이 호출 흐름에 통제권을 부여하지 않는다.
  • 어플리케이션 코드에서 객체의 생성/의존관계 관리의 책임을 덜 수 있어 코드 리팩토링의 복잡도를 낮춘다.
  • 스프링은 프레임워크 차원에서 관리하여 DI와 AOP를 사용할 수 있게 한다.

🌱 DI (Dependency Injection) : 의존성 주입

IoC 컨테이너에서 필요한 객체(Bean)를 주입하고 소멸시킨다.
각 레이어 간의 결합도를 낮춤으로써 코드 리팩토리를 용이하게 한다.

상위 모듈이 하위 모듈에 의존하지 않고 하위 모듈을 활용한다.
상위 모듈과 하위 모듈을 모두 추상화에 의존한다. 구체적인 것에 의존해서는 안된다.

사실 여기까지 아리까리 했다. 상위 하위가 뭐..? 추상.. 구체..? 😂
말이 젤 어려웠지.. Service와 ServiceImpl의 필요성을 보면서 좀 더 이해할 수 있었다.


🌱 @Service : Service Component

스프링 부트에서는 Service 컴포넌트를 어노테이션으로 사용하여 지정할 수 있다.
여기서 Service 컴포넌트가 개발에서 필요한 객체이다. 의존성 주입을 통해서 @Service를 사용할 수 있는 것이다. (조금씩 깨우쳐 가는 중 ^3^)


🌱 Service vs ServiceImpl

ServiceImpl 파일에서 함수의 구체적인 내용을 담고 있고, Service 파일에서는 ServiceImpl의 함수 정의만 담는다. 그리고 Controller는 Service에서 함수를 호출해온다.

ServiceImpl
(생으로 주입) ApplicationContext -> ServiceImpl

Service
(Proxy를 만들어 주입) ApplicationContext -> Service -> Proxy

"상위 모듈은 하위 모듈을 직접 참조해서는 안되고 그 추상화에 의존해야 한다" 위의 의존성 주입에 대한 내용에서 봤던 말이다. 이제는 조금 이해가 된다. 한 곳에 때려넣지 말고 모듈을 통해서 호출해서 활용해라. 활용하되 의존은 노노 ~.~ 클래스가 변경되면, 특히 강결합 상태에서는 다른 특성의 기능 추가가 힘들어지니까!


🌱 Kotlin Result

onSuccess & onFailure
함수형으로 호출/성공/실패(예외) 처리 기능
예시로 위와 같이 쓸 수 있다.

try-catch문 대용으로 사용하는 코틀린의 Result<>형을 배웠다. Result Type의 데이터를 반환하기 때문에 좀 더 깔끔한 코드를 짤 수 있다.


Service와 ServiceImpl을 Conroller에서 활용하는 코드로 리팩토링 했다. 유저를 조회하는 getUserByInfo 함수로 알아보자.

1주차 스터디 후 GetMapping을 활용하여 쿼리로 유저를 조회하는 getUserByInfo 함수를 생성했다.

> com.sohyeon.kopring.controller.HomeworkController.kt


    @GetMapping("/info")
    fun getUserByInfo(
            @RequestParam("part") part: String,
            @RequestParam("name") name: String
    ): String {
        return when (userList.find { it.name==name && it.part==part }) {
            null ->"조회 실패"
            else -> "조회 성공"
        }
    }

1주차 스터디 과제로 작성한 getUserByInfo 코드이다.

🌱 Controller

> com.sohyeon.kopring.controller.HomeworkController.kt


    @GetMapping("/info")
    fun getUserByInfo(
            @RequestParam("name") name: String,
            @RequestParam("part") part: String
    ): BaseResponse<String> {
        homeworkService.getUserByInfo(name, part)
                .onSuccess { return BaseResponse.success<String>(it) }
                .onFailure { return BaseResponse.failure(it.message ?: "해당 유저가 없습니다.") }
        throw RuntimeException("Unreachable Code")
    }

1주차 코드보다 코드가 좀 더 직관적이게 되었다. 서비스에서 유저 정보를 가져오면(onSuccess) 관련 데이터를 던지고, 실패하면(onFailure) 실패 메시지를 던진다. 함수의 구체적인 내용도 보이지 않는다. DI의 원칙에 한발짝!


🌱 Service

> com.sohyeon.kopring.service.HomeworkService.kt


interface HomeworkService {
    fun getUserByInfo(name: String, part: String): Result<String>
}

Controller가 호출해 온 Service에는 getUserByInfo 함수 정의가 존재한다. 여전히 구체적인 내용은 보이지 않는다. (나는 여기서 Proxy를 만들어 함수를 전달한 것으로 이해했다.) 또한 이 방식이 추상화에 의존하는 것으로 이해되었다.


🌱 ServiceImpl

> com.sohyeon.kopring.service.HomeworkServiceImpl.kt


@Service
class HomeworkServiceImpl: HomeworkService {
    private val userList: MutableList<HomeworkUser> = mutableListOf()

    override fun getUserByInfo(name: String, part: String): Result<String> {
        return when(val user = userList.find { it.isUserInfo(name, part) }) {
            null -> Result.failure(IllegalStateException("유저 조회 실패"))
            else -> Result.success("유저 조회 성공")
        }
    }

ServiceImpl에서 드디어 구체적인 함수 내용을 볼 수 있다. 쿼리로 받아 온 name과 part가 없으면 예외처리를, 존재하면 성공 Result를 던진다.


🌱 BaseResponse

> com.sohyeon.kopring.entity.BaseReponse.kt


data class BaseResponse<T>(
        val statusCode: Int,
        val success: Boolean,
        val data: T,
        val message: String
) {
    companion object {
    	// 트랜잭션 성공 시 함수
        fun <T> success(data: T) = BaseResponse<T> (
                statusCode = HttpStatus.OK.value(),
                success = true,
                data = data,
                message = "SUCCESS"
                )

		// 트랜잭션 실패 시 함수
        fun failure(message: String) = BaseResponse<String> (
                statusCode = HttpStatus.NOT_FOUND.value(),
                success = false,
                data = message,
                message = "FAILURE"
                )
    }
}

트랜잭션의 결과를 좀 더 간지나보이게 출력하기 위해 만든 파일이다 ㅋㅅㅋ

{
status: 200
success: true
data : { "성공 메세지" }
}

위와 같은 형식으로 이쁘게 이쁘게 결과 내기 위함 ^__^ 을 배웠다 ㅎ.ㅎ


🌱 실행결과

다음과 같이 코드에서 넘겨준대로 유저 정보 조회 성공 시의 결과를 볼 수 있었다.


성공 메세지 대신 유저 정보를 string으로 넘겨주면 위와 같은 결과도 가능했다!


Feeling


본격적으로 코프링을 배운 기분이었다! 코틀린의 유용한 함수와 Service와 ServiceImpl로 굳이 왜 나누어야 하는 지 배워 볼 수 있었다 😊 마냥 학교 프로젝트 과제나 인턴으로 일을 할 때는 해야 한다니까 생각하지 않고 컨트롤러에서 나누고 나누고 그랬었다. 하지만 이번 스터디를 통해서 핵심과 원리를 짚고 넘어간 후 코드를 만들어보니까 개념 이해와 함께 더 재밌게 다가왔다 ㅎㅎ 공부하자 ~.~

profile
쏘's 코딩·개발 일기장

0개의 댓글