본문 바로가기
기록해보기

Follow 기능 구현

by titlejjk 2023. 8. 24.

프로젝트를 진행중 Follow기능을 구현하기로해 공부한 내용을 적어본다.

 

사용한 기술

Java 11

MyBatis

Spring Frame work 2.7.14

MariaDB

IntelliJ Ultimate

 

나는 Backend 담당이여서 Back에 대한 코드밖에 제공하지 못한다..틈나는대로 적은거라 테스트는 PostMan으로 진행할 것이다.

 

요구사항

  1. 회원의 Follow 기능 구현
    팔로우 관계를 나타내는 테이블을 만들고., 팔로우 및 언팔로우 기능을 제공하는 API를 설계
  2. 팔로워 및 팔로잉 조회
    특정 사용자의 팔로워와 팔로잉 목록을 조회하는 API를 제공
  3. FK없이 테이블 설계
    Foreign Key를 사용하지 않고 테이블 간의 관계를 관리

먼저 팔로우 테이블 설계를 한다.

CREATE TABLE follows (
    follow_id INT AUTO_INCREMENT PRIMARY KEY, -- 팔로우 관계의 고유 ID
    follower_email VARCHAR(255) NOT NULL,     -- 팔로우하는 회원의 이메일
    following_email VARCHAR(255) NOT NULL,    -- 팔로우된 회원의 이메일
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 팔로우된 시각
);

테이블 설계시 생각해본 고려사항

 

1. follow_id (팔로우 관계의 고유 id)

각 팔로우 관계에 대한 고유 식별자

데이터베이스에서 각 팔로우 관계를 유일하게 식별

AUTO_INCREMENT를 사용

 

2. follower_email(팔로우하는 회원의 이메일)

팔로우를 하는 회원의 이메일 주소

userEmail을 사용하여 사용자를 식별

이메일은 사용자를 유일하게 식별할 수 있는 값으로 키를 사용

 

3. following_email(팔로우된 회원의 이메일)

팔로우를 당하는 회원의 이메일 주소

팔로우 하는 회원이 누구인지 식별하기 위해 사용

 

4. created_at(팔로우된 시각)

팔로우 관꼐가 형성된 시각을 나타낸다.

시간 순서대로 팔로우 관계를 분석하거나, 특정 시점의 팔로우 상태를 확인

DEFAULT CCURRENT_TIMESTAMP를 사용하여 자동으로 현재 시각이 저장되도록 설계

 

팔로우하는 회원과 팔로우 되는 회원 간의 관계를 간단하게 표현하며, 각 컬럼은 팔로우

관계의 중요한 속성을 나타내며, 이를 통해 팔로우/언팔로우 기능, 팔로워,팔로잉 조회등의

기능을 구현 가능하다.

이메일을 사용한 이유는 회원의 유일하게 식별할 수 있는 키로 사용하기 위함이며 회원간에

중복되지 않는 유용한 식별자로 쓰일수 있을 것 같아 사용하기로 했다.

 

다음으로 DB사용을 위한 FollowMapper를 만들어본다.

FollowMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.project.project.user.dao.UserMapper">

    <!-- 팔로우 추가 -->
    <insert id="inserFollow">
        INSERT INTO follows (follower_email, following_email)
        VALUES (#{followerEmail}, #{followingEmail})
    </insert>

    <!-- 팔로워 조회 -->
    <select id="findFollowers" resultType="com.project.project.user.dto.FollowDto">
        SELECT * FROM follows WHERE follower_email = #{userEmail}
    </select>

    <!-- 팔로잉 조회 -->
    <select id="findFollowings" resultType="com.project.project.user.dto.FollowDto">
        SELECT * FROM follows WHERE following_email = #{userEmail}
    </select>
    
    <!-- 팔로우 삭제 -->
    <delete id="deleteFollow">
        DELETE FROM follows WHERE follower_email = #{followerEmail} AND following_email = #{followingEmail}
    </delete>
</mapper>

 

  1. 팔로우 추가 (insertFollow)
    새로운 팔로우 관계를 데이터베이스에 추가한다.
    "follower_email"과 "following_email"을 파라미터로 사용하여 팔로우 관계를 지정

  2. 팔로워 조회 (findFollowers)
    특정 사용자가 팔로우하는 모든 팔로워를 조회
    "follower_email"에 해당하는 이메일을 사용하여 팔로워 목록을 가져온다.

  3. 팔로잉 조회 (findFollowing)
    특정 사용자가 팔로우되는 모든 팔로잉을 조회한다.
    "following_email"에 해당하는 이메일을 사용하여 팔로잉 목록을 가져온다.

  4. 팔로우 삭제 (deleteFollow)
    기존 팔로우 관계를 가진 row를 삭제한다.
    "follower_email"과 "following_email"파라미터를 사용하여 어떤 팔로우 관계를 삭제할지 지정

Mapper XML은 기본적으로 MyBatis를 사용하여 SQL쿼리를 Java코드와 연결하는데 사용한다. 팔로우, 팔로워 조회, 팔로잉 조회, 삭제 등 기본 CRUD작업을 위해 위와 같이 쿼리를 정의 했다.

 

FollwoDto 만들기

 

@Getter
@Setter
public class FollowDto {

    private int followId; //팔로우 관계의 고유 ID
    private String followerEmail; //팔로우하는 회원의 이메일
    private String followingEmail; //팔로우된 회원의 이메일
    private String createdAt; //팔로우가 된 시각
}

 

"follweId"는 팔로우 관계의 고유 ID로 쓰기 위해 만들었다. 나중에 데이터베이스 내에서 이 ID를 사용하여 특정 팔로우 관계를 찾을 수 있다.

"followerEmail"은 팔로우를 실행한 회원의 이메일로 사용할 것이며 이메일을 사용하여 팔로우를 한 회원을 식별하고 관련 정보를 검색,

"followingEmail"은 팔로우된 회원의 이메일 기능으로써 팔로우를 당한 회원의 관련 정보를 검색할 수 있다.

 

Dto는 데이터베이스의 follow테이블과 일대일로 매핑되는 객체로, 테이블의 각 칼럼을 클래스 속성으로 나타내준다.

가장 일반적인 방법이기도 하며 애플리케이션 코드가 데이터베이스 구조와 밀접하게 연관되지 않도록 나름 설계해서 유지 보수 및 확장성을 고려해서 설계해 보려고 했다.

 

FollowDao Interface 만들기

 

@Mapper 어노테이션을 통해서 mapper xml과 연결해보겠다.

@Mapper
public interface FollowMapper {

    //Follow add
    void insertFollow(FollowDto followDto);

    //Select Follower
    List<FollowDto> findFollowers(String userEmail);

    //Select Following
    List<FollowDto> findFollowings(String userEmail);

    //Delete Follow
    void deleteFollow(FollowDto followDto);
}

insertFollow메서드는 팔로우추가

findFollowers메서드는 팔로워 조회

findFollowings메서드는 팔로잉 조회

deleteFollow메서드는 팔로우를 삭제

 

DAO를 통해 데이터베이스 연산을 캡슐화하여 상호 작용을 한다. JPA는 써보질 않아서 모르겠지만 이렇게 데이터베이스와 상호 작용을 캡슐화하여 코드의 재사용성과 유지 보수성을 향상시키는 점이 장점이라 생각이 들었다.

 

FollowServiceImpl Class 만들기

Service Interface는 생략하고 진행했습니다(전 물론 만들었습니다!)

Dao에 있는 FollowMapper interface를 의존성으로 추가해서 작성해준다.

@Service
@RequiredArgsConstructor
public class FollowServiceImpl implements FollowService{

    //FollowMapper 의존성 주입
    private final FollowMapper followMapper;

    //사용자 팔로우
    @Override
    public void followUser(FollowDto followDto) {
        followMapper.insertFollow(followDto);
    }

    //팔로워 목록 조회
    @Override
    public List<FollowDto> getFollowers(String userEmail) {
        return followMapper.findFollowers(userEmail);
    }

    //팔로잉 목록 조회
    @Override
    public List<FollowDto> getFollowings(String userEmail) {
        return followMapper.findFollowings(userEmail);
    }

    //사용자 언팔로우
    @Override
    public void unfollowUser(FollowDto followDto) {
        followMapper.deleteFollow(followDto);
    }
}

각 메서드별 설명은 위와 비슷합니다.

 

다음 마지막 6번으로 Controller입니다.

이 경우에는 위에 따로 Service Class와 Mapper를 만들어주듯이 따로 만들진 않고 기존에 사용하던 UserController에 함께 작성해주었습니다.

   //팔로우
    @PostMapping("/follow")
    public ResponseEntity<Void> followUser(@RequestBody FollowDto followDto){
        followService.followUser(followDto);
        return ResponseEntity.ok().build();
    }

    //팔로우 목록 조회
    @GetMapping("/followers/{userEmail}")
    public ResponseEntity<List<FollowDto>>getFollowers(@PathVariable String userEmail){
        List<FollowDto> followers = followService.getFollowers(userEmail);
        return ResponseEntity.ok(followers);
    }

    //팔로잉 목록 조회
    @GetMapping("/followings/{userEmail}")
    public ResponseEntity<List<FollowDto>> getFollowings(@PathVariable String userEmail){
        List<FollowDto> followings = followService.getFollowings(userEmail);
        return ResponseEntity.ok(followings);
    }

    //언팔로우
    @DeleteMapping("/unfollow")
    public ResponseEntity<Void> unfollowUser(@RequestBody FollowDto followDto){
        followService.unfollowUser(followDto);
        return ResponseEntity.ok().build();
    }

컨트롤러에서는 클라이언트의 HTTP요청을 처리하고 적절한 응답을 반환하도록 설계했습니다.

클라이언트의 요청을 처리하는 서버의 진입점으로써 적절한 엔드포인트를 정의해주었습니다.

200ok가 뜬것을 확인했습니다.

 

댓글