블로그
전체 42025. 03. 30.
0
[인프런 워밍업 클럽 3기 - 백엔드 프로젝트] 4주차 발자국
워밍업 클럽 마지막 주차가 되었다..!! ✅ 강의4주차에서는 구글 클라우드 플랫폼을 활용해서 지금까지 만들었던 프로젝트를 배포해보았다!도커와 도커파일을 활용해서 프로젝트를 빌드했다.구글 클라우드 플랫폼을 통해서 인스턴스를 생성했다.생성한 인스턴스에서 만들었던 프로젝트의 도커 컨테이너를 실행했다.도메인을 구입하고 연결해보고 HTTPS도 적용하는 방법을 배웠다. ✅ 미션마지막 미션 두가지는 이번주차 강의 내용을 따라가기만 하면 문제없이 해결할 수 있었다.[미션6] 가상 프로필을 나의 프로필로 바꾸기[미션7] 내 포트폴리오 도메인 공유하기아직 나의 프로필에 쓸 내용을 많이 정리하지 못해서 구색만 갖추게...되었다..😓마지막 포트폴리오 도메인 공유에서는 직접 도메인을 구매할 수도 있었지만 현재 나에게 도메인이 필요한 상황이 많지 않아서 무료 도메인 호스팅해주는 웹사이트(Duck DNS)를 활용했다..! 🍀 마무리아쉬운 점은 미션7을 제 시간안에 제출하지 못한 점이다. 회사일이 바빠서 미션7과...중간점검 2차 참여를 놓치게 되었다....너무 아쉬웠다..🥺미니 프로젝트도 좀 더 디벨롭해보고자 했는데...그래도 워밍업 클럽을 진행하면서 평소라면 초반에 달리다가..용두사미가되어...완강까지 쉽지 않았을..ㅎ 인프런 강의를 한달안에 제대로 완주할 수 있어서 좋았다...!알찬 3월을 보낼 수 있게 이런 좋은 이벤트(?)를 열어준 인프런과 정보근 코치님께 감사드린다는 말을 마지막으로 4주차 발자국을 마치도록 하겠습니다...!🍀
2025. 03. 23.
0
[인프런 워밍업 클럽 3기 - 백엔드 프로젝트] 3주차 발자국
워밍업 클럽도 벌써 3주차가 되었다. ✅ 강의3주차에는 어드민 개발을 마무리 짓는 내용이었다.공통 Exception과 Advice를 개발하고 적용했다.각 CRUD 기능을 개발하였다.나머지 화면단은 템플릿 라이센스 이슈로 조금의 문제가 있어서 이번에는 넘어가게되었다. ✅ 미션3주차는 [미션5] 삽입, 수정, 삭제 REST API 만들기가 메인이었다.미션의 조건은 플레이리스트 삽입, 수정, 삭제로 잡았지만,초기에 계획했던 API는 아래와 같아서 미션3에서 완성했던 음원 목록 조회쪽 제외 나머지 기능을 완성하는 것을 목표로 했다.구현하면서 조금 헤맸던 포인트는 두가지 정도가 있었다. 그 포인트들에 대해 기록하면서 진행했는데 발자국에도 조금 소개해보려고 한다. (강의에서 나왔던 내용이기도 하다..😅) 플레이리스트 생성 시 연관관계 생기지 않는 문제 fun createPlaylist(request: CreatePlaylistRequest): Playlist { val host = memberRepository.findById(request.hostId) .orElseThrow { IllegalArgumentException("해당 멤버가 존재하지 않습니다.") } val playlist = Playlist( member = host, title = request.title, playlistImage = request.playlistImage ) return playlistRepository.save(playlist) } 처음에 만든 플레이리스트 생성 코드이다. Playlist를 생성할 때, TrackPlaylist 데이터를 생성해주는 로직 자체가 없다.JPA의 연관관계 설정은 엔티티 간 관계만 정의할 뿐, 연관된 데이터를 자동으로 생성해 주진 않는다. 반드시 직접 TrackPlaylist 객체를 생성하고 저장해 줘야 한다. @Transactional fun createPlaylist(request: CreatePlaylistRequest): Playlist { val host = memberRepository.findById(request.hostId) .orElseThrow { IllegalArgumentException("해당 멤버가 존재하지 않습니다.") } val playlist = Playlist( member = host, title = request.title, playlistImage = request.playlistImage ) playlistRepository.save(playlist) request.trackIds.forEach { trackId -> val track = trackRepository.findById(trackId) .orElseThrow { throw BadRequestCustomException("해당 음원 트랙이 존재하지 않습니다.") } val trackPlaylist = TrackPlaylist( track = track, playlist = playlist ) playlist.addTrackPlaylist(trackPlaylist) } return playlist } 수정된 코드이다. 추가로 Playlist 엔티티에도 설정이 필요하다.CascadeType.ALL과 orphanRemoval=true 옵션을 추가해줬으므로, 편의 메서드를 호출하고 Playlist를 저장할 때 TrackPlaylist까지 자동으로 영속화하도록 했다.orphanRemoval default는 false이다.부모 엔티티에서 자식 엔티티가 제거된 경우, 이 자식 엔티티를 더 이상 참조하는 곳이 없어지면 JPA가 자동으로 자식 엔티티를 DB에서 삭제해주는 옵션이다.즉, 부모와의 연결이 끊어지면 자식 데이터가 자동으로 삭제되는 것이다. 플레이리스트 상세 조회시 오류playlist를 id기반으로 가져오려고 했는데 오류가 발생했다. JPA lazy 로딩 이슈였다.현재 Playlist 엔티티에서 host(Member)와 trackPlaylists 필드가 모두 FetchType.LAZY로 설정되어 있다.이로 인해, 영속성 컨텍스트가 종료된 이후에 프록시 객체를 접근하려고 하면 오류가 발생한다.@ManyToOne(targetEntity = Member::class, fetch = FetchType.LAZY) var host: Member = member @OneToMany(mappedBy = "playlist", fetch = FetchType.LAZY) var trackPlaylists: MutableList = mutableListOf()Fetch Join을 사용해야한다. PlaylistRepository에 다음과 같은 메서드를 추가하여 Fetch Join으로 데이터를 즉시 로딩하게 한다.interface PlaylistRepository : JpaRepository { @Query("select p from Playlist p join fetch p.host left join fetch p.trackPlaylists tp left join fetch tp.track where p.id = :id") fun findByIdWithDetails(@Param("id") id: Long): Playlist? } fun getPlaylist(playlistId: Long): Playlist { return playlistRepository.findByIdWithDetails(playlistId) ?: throw BadRequestCustomException("해당 플레이리스트가 존재하지 않습니다.") } 🍀 마무리미니 프로젝트도 거의 마무리가 되었다. 사실 기능 구현보다 테스트 코드 작성이 생각보다 훨씬 어렵게 느껴진다. 익숙치 않아서 그런가....뭔가 따로 테스트 코드에 대해서 학습이 필요할 것 같다. 그래도 미니 프로젝트를 통해 어떤 방식으로 구현하고 테스트를 작성해야할지 아주 조금...감이 오기 시작했다. 이제 다음주면 마지막 주차이다. 마지막 주도 잘 마무리해서 유종의 미를 거뒀으면 좋겠다. 화이팅!!!
2025. 03. 16.
0
[인프런 워밍업 클럽 3기 - 백엔드 프로젝트] 2주차 발자국
인프런 워밍업 클럽 본격적으로 실습에 들어가게 되는 주였다.또 온라인 라이브도 있었다. ✅ 강의2주차 강의부터는 본격적으로 프로젝트를 구현하는 내용이었다.repository를 개발하고 테스트 코드도 작성해보며 성능 개선까지 해보았다.서비스와 컨트롤러도 개발하여 API를 만들고 직접 확인해보았다. 또한 테스트 코드도 작성하였다.Thymeleaf를 활용해 화면단의 템플릿을 부트스트랩을 통해 가져오고 수정하여 프로젝트에 맞게 구현하였다.컨트롤러의 앞단에서 동작하는 인터셉터를 구현하였다. ✅ 미션2주차 미션은 미션3, 4 두가지 였다.(마감기준보다는 시작기준으로..ㅎㅎ)[미션3] REST API 설계하기[미션4] 조회 REST API 만들기[미션3]은 내가 미니 프로젝트로 하기로 결정한 플레이리스트 공유 프로젝트에서 사용될 간단한 CRUD를 예상하고 API를 설계해서 제출했다.고민 포인트가 하나 있었다면, 플레이리스트 생성할 때 필연적으로 음원 목록들의 id가 배열로 들어갈 것이기 때문에 음원 목록 조회까지 잊지 말아야했다는 점이다. 또 사용자 본인만의 플레이리스트를 조회하고 싶은 니즈가 있을 것이고, 전체의 모든 플레이리스트를 보고 싶은 니즈가 있을 것 같아서 조회 API를 두 개 둬봤다. 문서는 Postman documentation을 활용해 제출했다. ㅎㅎ [미션4]는 발자국을 작성하는 지금 시점에서 아직 다 마치지는 못했다. 플레이리스트 조회의 경우 강의에서처럼 데이터베이스 초기화 코드를 통해 임의로 넣어서 테스트 해볼 수 있겠지만, 뭔가 직접 CRUD를 완성한 뒤 조회하고 싶었다. 그래서 이번 조회 API 과제에서는 음원 목록을 조회하는 API를 먼저 다루는 것으로 결정했다. 여기서 음원 목록을 어떻게 다룰것이냐라는 고민도 있었는데, 음원은 어딘가의 오픈 API로 끌어오거나 크롤링으로 가져오면 베스트일 것 같았다. 하지만 이번에는 그렇게까지는 하지 않고 간단하게 xml 파일에 음원 목록을 몇개 정리해서 데이터베이스 초기화 코드에서 읽고 넣어주는 것으로 결정했다. xml 파일은 gpt의 도움을 받았다 ㅎㅎ 간단하게 jackson-dataformat-xml을 사용해서 다음과 같이 코드를 작성했다. // track 초기화 - xml 파일로 데이터 추가 val xmlMapper = XmlMapper() val inputStream = javaClass.getResourceAsStream("/track-data.xml") ?: throw IllegalStateException("track-data.xml not found") val tracks = xmlMapper.readValue>(inputStream) trackRepository.saveAll(tracks) 스프링 애플리케이션 실행 후 결과를 확인해보면 다음과 같이 데이터가 잘 등록된 것을 확인할 수 있었다. 마지막으로 음원 목록 조회 API 코드까지 완성 후 포스트맨으로 테스트한 결과이다. 이제 남은 건 테스트 코드 작성이다...! 빨리 마무리하고 미션 제출해야겠다....🏃♂ 🍀 마무리확실히 강의를 들으면서 프로젝트를 따라가는 것과, 내가 생각하는 프로젝트에 적용해보는 것은 확연히 달랐다. 강의를 들으면서 할 때는 술술 넘어가던 부분이 혼자 프로젝트에 적용해보려고 하니 또 기억이 안나고, 잘 안되기도 했다..! 그래서 이 워밍업 클럽을 통해 강의 + 나만의 프로젝트를 해볼 수 있어서 참 좋고 얻어가는게 많을 것 같다. 다음주도 화이팅!!
2025. 03. 09.
0
[인프런 워밍업 클럽 3기 - 백엔드 프로젝트] 1주차 발자국
인프런 워밍업 클럽이 시작되었다..!평소 코틀린 + 스프링에 관심이 있었는데 좋은 기회로 참여할 수 있어서 너무 좋다.🤗또 평소 nestjs만 사용했어서 스프링은 처음이라 기대가 되었다. ✅ 강의1주차 강의는 이론 위주였다.강의에서 같이 만들 프로젝트를 미리 살펴보았다.웹과 데이터베이스 개발 기본 개념에대해서 다루었다.프로젝트 초기 세팅과 엔티티 뼈대를 만들었다.코틀린 문법, 나아가 자바 문법도 안쓴지 오래되어서 따라가기 벅차지 않을까 걱정했는데 강의에서 간단한 문법만 사용하고, 잘 설명해주셔서 문제없었다. 또 진행하면서 스프링의 동작에 대해서도 알아갈 수 있어서 참 좋았다. 스프링이 처음이어서 강사님이 설명해주시는 내용애 대해 강의 자료에도 있지만, 그보다 더 자세하게 나만의 강의 노트에 정리했다.강의 진도에 밀리지 않고 미리 듣고 과제를 할 때 생각하는 시간을 더 갖자는게 목표였는데, 첫주차 강의는 진도보다 미리 다 들어서 목표 달성에 성공했다. 하지만 이 2주차에도 이어가려면 지금 발자국을 작성하는 이 시간에도 진도가 나가있어야하는데 많이 나가진 못해서 아쉽다. 이 발자국을 다 작성하고 나서 더 공부할 것이다.ㅎㅎ ✅ 미션1주차 미션은 두가지 였다.[미션1] 깃허브 리포지토리에 프로젝트 올리기[미션2] 테이블 설계하기 [미션1]은 강의에서 코틀린 프로젝트를 만들어본 것과 똑같이 프로젝트를 생성하고 올리기만하면 되어서 간단했다. 그보다 어떤 프로젝트를 할 지 고민이 많았는데, 평소 음악을 좋아해서 플레이리스트 공유 서비스 프로젝트로 선택했다. 비슷한 프로젝트를 프론트로서 참여한 적이 있는데 백엔드 입장에서 설계해보면 또 다른 느낌일 것 같다는 이유도 있다. [미션2]는 테이블 설계였다. 먼저 사용자는 여러개의 플레이리스트를 만들 수 있다는 1대다 관계에서 시작했다.그런데 음악 데이터는 어떻게할지 고민이되었다. 일단 사용자와 플레이리스트 관계에 집중하고 음악 데이터는 초기 DB에 gpt 등을 이용해 데이터를 추출해 가지고 있기로 결정했다. 그렇게 member(사용자), Track(음원), Playlist(플레이리스트)의 새 개의 테이블을 먼저 설계했다. 하지만 설계하다보니 고민거리가 있었다.member -> playlist (1:N)사용자(member) 와 플레이리스트(playlist) 간의 관계를 설계할 때, 처음에는 member 테이블에도 playlist의 FK를 두는 것을 고려했었다. 하지만 이럴 경우 member가 여러 플레이리스트를 가질 때마다 동일 사용자의 정보가 중복되어 여러 행이 생성된다. 그래서 member가 아닌 playlist 테이블 쪽에만 member의 FK를 두는 형태로 수정했다. playlist track (N:M) playlist에 여러 track_id가 여러개 물릴 수 있으니 해당 테이블에 같은 id로 여러 행이 반복되었다. 따라서 playlist_id와 track_id를 FK로 가지는 중간 테이블 Track_Playlist를 만들게되었다. 중간 테이블이 생기니 playlist와 track 테이블에 중복되는 데이터가 사라졌다.🍀 마무리인프런 워밍업 클럽 1주차가 마무리되었다. 2주차 부터는 강의도 과제도 본격적으로 개발에 들어가게된다. 더 열심히 참여할 수 있도록 해야겠다! 화이팅!
백엔드
・
인프런워밍업클럽3기