본문 바로가기

프로젝트

[프로젝트] 부동산 실거래가 정보 제공 사이트

한학기 동안 배운 내용을 기반으로 2명이서 진행하는 관통 프로젝트를 완성했다.

사실 많이 어렵거나 고난이도의 프로젝트는 아니었지만 프로젝트를 하면서 배운 내용이 몇 가지 있어 정리해봤다.

우선 프로젝트 요구 사항을 정리해보면

 

1. 공공데이터(부동산 실거래가 & 매물 데이터 등등)를 활용하기

2. (프론트) 지도 API를 활용하기

3. (백) open AI 활용해서 AI 활용한 서비스 추가하기

4. 기본 CRUD 활용해서 회원관리, 게시판 관리 기능

 

여기다가 추가적으로 REST API 활용하고, Spring Security와 JWT 활용해서 보안성을 높이는 것까지 구현했다.

 

System Architecture

 

실제 발표 때 썼던 PPT slide

 

2인 프로젝트다보니 프론트 백 구분 없이 기능 단위로 그냥 잡히는 대로(ㅎㅎ) 나눠서 했는데 내가 주로 담당한 건

 

1. 관리자 페이지 (공지사항, 신고 관리, 회원 관리)

2. 지역 커뮤니티 (법정동 별 익명 게시판, 좋아요, 신고 기능)

3. 관심 지역 / 매물 관리

4. AI 인사이트 (투자 보고서 생성)

5. Spring Security & JWT

 

이렇게로 정리할 수 있을 것 같다.

이게 사실 욕심을 안 부리면 진작에 완성되었을텐데 또 하다보니까 이게 필요할 것같고... 저것도 필요할 것 같고...

또 주변에서 도와주는 손길(or 훈수)이 많았어서 ((사실 너무 고마웠어요.))

개발 마감 날까지 코드 보고 기능 추가하고 수정하고... 그랬다.

 


 

디자인 패턴??

프로젝트를 통해서 배운 점이 있다면..

 

먼저 스프링 프레임워크에 가득가득 녹아있는 디자인 패턴! 을 좀 익숙하게 활용하게 되었다.

 

스프링 프레임워크에서 싱글톤 패턴이나 DI(의존성 주입), MVC 패턴을 기반으로 @Service, @Repository처럼 빈으로 등록된 객체는 기본적으로 싱글톤으로 관리되니까, 매번 객체를 새로 만들 필요 없이 하나로 돌려 쓸 수 있었다.

 

처음에는 이걸 왜 굳이 이렇게 하나 싶었는데 추가 기능을 확장할 때 필요한 데이터(DTO) 기반으로 미리 만들어 놓은 Repository를 사용해서 DB와 소통하는 DAO에 메서드만 추가하면 되니까 나중에 가서는 API 하나 만드는데 진짜 금방 만들어졌다.

나중에 서비스 유지보수가 필요할 때는 이게 필수적이겠구나 싶었다.

 

이제 와서 찾아보니 테스트나 AOP 작성하는 건 프록시 패턴, 스프링 시큐리티 부분 구현할 때는 상위 클래스(추상 클래스)에서 정의해두고, 세부적인 단계 일부는 하위 클래스에서 구현한다는 점에서 템플릿 메서드 패턴에 해당된다는데...

 

디자인 패턴에 대해서 조금 더 공부해서 왜 이 디자인 패턴이 필요하고, 효율적인지 생각하면서 개발할 필요성이 있는 것 같다.

 

* 디자인 패턴 간략 정리

더보기

✅ 싱글톤 패턴 (Singleton)

  • 하나의 인스턴스만 생성해 전체에서 공유
  • 스프링에서는 @Service, @Repository 등으로 등록한 빈이 기본적으로 싱글톤으로 관리됨
  • 메모리 효율과 상태 관리에 유리, 특히 DI와 함께 사용할 때 효과적

✅ DI (의존성 주입) 패턴

  • 객체를 직접 생성하지 않고 외부에서 주입
  • 테스트 편의성, 의존성 관리, 결합도 낮추기에 효과적
  • 스프링 컨테이너가 알아서 의존 객체를 주입해줌

✅ MVC 패턴 (Model-View-Controller)

  • 역할을 분리해서 유지보수성과 확장성을 높임
    • Controller: 요청 처리
    • Service / Model: 비즈니스 로직
    • View: 화면 렌더링
  • 웹 개발 구조의 기본

✅ 템플릿 메서드 패턴 (Template Method)

  • 전체 알고리즘 흐름은 상위 클래스에서 정의하고, 세부 단계는 하위 클래스가 구현
  • 스프링 시큐리티 필터나 인증 로직에서 자주 등장
  • 공통 로직 + 커스터마이징이 필요한 상황에서 유용

✅ 프록시 패턴 (Proxy)

  • 대리 객체를 통해 원래 객체에 접근하거나, 추가 작업을 수행
  • AOP, 로깅, 캐싱, 트랜잭션 처리 등에 활용됨
  • 실제 로직 앞뒤로 부가 기능을 끼워넣을 수 있음

 


 

조회수 로직 관련 고민들

이번 프로젝트를 진행하면서 제일 많이 공감했던 말이 "게시판 하나라도 똑바로 만들어라!"는 말이다.

그냥 CRUD만 생각하면 그게 뭐가 어렵나 싶었는데,

우리가 일상에서 편하게 쓰는 커뮤니티 기능이 뭐하나 그냥 되는 게 없다는 걸 깨달았다.

예를 들어,

- 이미지 업로드 기능: 이미지를 어디에 저장할 것인지, 다수의 이미지 업로드-로드를 어떻게 처리할 것인지, 데이터 관리는 어떻게 할 것인지.

- 댓글 기능: 대댓글, 대댓글의 대댓글... 이걸 어떻게 처리할 것인지, 유저가 탈퇴하면 해당 댓글이 지워져야하는지 (논리 삭제) 그렇지 않다면 어떻게 DB 연관관계를 구성해야하는지

 

등등등...

근데 그 중에서도 조회수 기능 구현하는 거에 꽂혀서 페어 분이랑 나랑 계속 토론했었다.

게시글을 상세 조회를 할 때마다 조회수가 올라간다고 하면, 그 페이지에서 새로고침할 때마다 혹은 중복으로 그 게시글을 조회할 때마다 조회수가 올라가게 되는데 그건 아니라고 생각이 모아졌다. (물론 우리 프로젝트 내에서 조회수가 그렇게 의미있는 기능은 아니었지만... 그래도 새로고침할 때마다 조회수가 올라가는 건 사용자가 확인할 수 있는 거니까 수정이 필요하다고 생각함.)

그래서 방법을 찾아보다가 일단은 쿠키를 사용해서, 사용자가 읽은 게시글을 기록하고 쿠키에 그 게시글이 없을 경우에만 조회수를 올리는 걸로 했음.

이때, 클라이언트에서 읽고 쓸 수 있는 document 쿠키보다는 http only 쿠키를 사용하는 게 xss 방지 차원에서 더 낫다고 한다.

물론 이게 완전한 XSS 방지를 할 수 있는 건 당연히 아니다. (클라이언트 코드 삽입 자체를 막는 건 아니기 때무네...)

 

여기까지 개발하고 완성하는 방향으로 했는데, 조금 더 찾아보니까 Redis를 사용한다면 TTL (Time To Live)으로 같은 유저가 같은 게시글을 TTL 시간 내에 조회하면 조회수 증가하지 않는 방식으로 해결하는 것이 좀 더 나은 방법인 것 같았다.

 

낫다고 생각하는 이유?

1. "조회수"라는 로직에 어느정도 자원을 투자할 정도로 비즈니스 로직에서 중요하다면 어디까지나 조작 가능한 클라이언트 상태가 아니라 서버에서 기록되어야 한다고 생각됨.

2. 기존 쿠키에는 사이즈 제약(하나 당 4KB)이 있기 때문에 지나치게 많은 글을 읽게 되면 제대로 기능하지 못하게 될 가능성 존재

3. 다 떠나서 성능적인 측면에서 쿠키를 순회하면서 조회하는 것보다 in-memory & key-value 구조인 Redis를 이용하는 게 유리.

 

이 부분을 해결하면서 느낀 점은... 개발은 도구이기 때문에 어디까지나 비즈니스 로직에 맞게 설계하는 게 중요하다는 점이다.

조회수를 기반으로 실시간 인기 게시글, 동영상 이런 식으로 활용하거나, 사용자나 게시글 수가 많아진다면 이 데이터를 안정적이고 효율적으로 관리가 필요하다면 Redis TTL 방식을 활용하여 서버 측에서 이 데이터를 관리하는 게 맞다.

하지만 그리 중요하지 않고 그냥 사용자가 이 글을 얼마나 읽었는지 정도 파악만 하자, 정도의 느낌이라면 기존 방식으로도 충분할 수도 있다.

 

실제로 커뮤니티 돌아보다가 아직도 새로고침 할 때마다 조회수가 올라가는 커뮤니티를 발견했다(ㅋㅋ) 


효율적인 SQL 쿼리의 중요성

관심 매물 / 지역에서 사용자가 관심 아이템으로 등록한 데이터를 요약하는 페이지

 

사용자가 관심 지역과 매물을 모아두고 한 번에 확인하는 페이지가 존재한다.

현재 로그인한 회원이 관심 매물 / 지역으로 등록한 list를 순회하면서 해당 아이템에 회원이 관심있어할 만한 데이터를 요약하고 싶은데! (ex. 특정 아파트 평균 거래가, 특정 지역 평균 거래가 등등...)

처음에 그냥 무식하게 조인 쿼리를 때리고서는 시간이 너무 오래 걸려서 데이터도 정제하고, 인덱싱도 하고, 이렇게 저렇게 쿼리 수행 성능 자체를 개선할 생각만 했었다.

그치만 당연하게도, 조인 연산은 곱 연산을 수행하기 때문에 조인 연산을 수행하기 전에는  조인 전에 서브쿼리나 WHERE 조건을 이용해서 튜플 수를 줄이는 전략이 중요하다. (이론으로는 알고 있었는데 막상 급하게 개발하다보니까 까먹고 있었음ㅋㅋㅋ.. 너무 허무해...)

아무튼 이렇게 쿼리를 다시 바꾸니까 실제로 서비스에 적용 가능한 성능이 나왔다.

 

결국 백엔드 개발에서 가장 중요한 건 데이터라는 생각이 들었다.

 

1) 어떻게 구조화해서 관리할 것인지, 2) 어떻게 가져와서 가공할 것인지.

 

그래서 다음에 프로젝트를 진행할 때에는 DB 관련해서 조금 더 다뤄보면 좋겠다는 생각이 들었다. .oO(공부할게 왜케 많냐.. 듀듀)


 

그밖에...

사소하게 고민했던 부분 중 하나가 AI 분석 리포트에서 로딩 시간이 생각보다 꽤 길다는 점이다.
이 부분도 RAG 구조에서 Redis 같은 캐싱 서버를 활용하면, 동일한 요청에 대해서는 캐싱된 데이터를 재사용해서 응답 속도를 개선할 수 있겠다는 생각이 들었다.

(부동산 실거래가 데이터가 실시간으로 변경될 가능성 보다는 비슷한 사용자가 같은 분석 보고서를 요청하는 경우가 더 많을 테니...)

 

또한 논리 삭제 개념이 생각보다 다양한 곳에 적용될 수 있다는 점을 배웠다.

내가 처음 논리 삭제를 접한 건, 회원 데이터를 아예 삭제하기보다는 is_deleted 같은 플래그 컬럼을 두고,
해당 유저가 탈퇴했는지를 판단하는 방식으로 회원 탈퇴 기능을 구현하는 거였다.

그런데 이번 프로젝트에서 게시글 신고 기능을 구현하면서, 이런 논리 삭제 개념이 관리자 단에서의 콘텐츠 관리에도 적절할 수 있겠다는 생각이 들었다.

예를 들어, 특정 게시글이 신고되었다고 해서 바로 삭제해버리기보다는, 논리 삭제로 처리해두면 나중에 필요에 따라 복구하거나, 신고 이력을 관리하는 데 더 유리할 수 있다.

 


이번 프로젝트 하면서 제일 많이 깨달은 건 단순히 기능을 구현하는 건 AI와 함께라면 약간의 노력으로 누구나 다 할 수 있다는 점이다.

때문에, 좋은 개발자가 되기 위해서는 이 서비스를 어떻게 하면 더 효율적이고, 편리하게 만들 수 있을지 고민할 줄 아는게 훨씬 중요할 것 같다.

다음 프로젝트에서도 이런 부분에 더 신경 쓰면서 개발해야지.. 싶다.

공부도 더 많이 하구 !