페스트캠퍼스 프로젝트

[어드민] 도메인 설계

hyeongjune 2025. 1. 6. 00:01

 

어드민 서비스의 필요한 도메인 설계 

 

UserAccount domain 설계에서의 권한 설정

package com.fastcampus.projectboardadmin.domain;

import com.fastcampus.projectboardadmin.domain.constant.RoleType;
import com.fastcampus.projectboardadmin.domain.converter.RoleTypesConverter;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;


@Getter
@ToString(callSuper = true)
@Table(indexes = {
        @Index(columnList = "email", unique = true),
        @Index(columnList = "createdAt"),
        @Index(columnList = "createdBy")
})
@Entity
public class UserAccount extends AuditingFields {
    @Id
    @Column(length = 50)
    private String userId;

    @Setter
    @Column(nullable = false)
    private String userPassword;

    @Convert(converter = RoleTypesConverter.class)
    @Column(nullable = false)
    private Set<RoleType> roleTypes = new LinkedHashSet<>(); // "1,2,3" -> table column

    @Setter
    @Column(length = 100)
    private String email;

    @Setter
    @Column(length = 100)
    private String nickname;

    @Setter
    private String memo;

    protected UserAccount() {

    }

    private UserAccount(String userId, String userPassword, Set<RoleType> roleTypes , String email, String nickname, String memo , String createdBy) {
        this.userId = userId;
        this.userPassword = userPassword;
        this.roleTypes = roleTypes;
        this.email = email;
        this.nickname = nickname;
        this.memo = memo;
        this.createdBy = createdBy;
        this.modifiedBy = createdBy;
    }

    public static UserAccount of(String userId, String userPassword, Set<RoleType> roleTypes , String email, String nickname, String memo) {
        return new UserAccount(userId, userPassword, roleTypes ,email, nickname, memo, null);
    }

    public static UserAccount of(String userId, String userPassword, Set<RoleType> roleTypes ,String email, String nickname, String memo , String createdBy) {
        return new UserAccount(userId, userPassword, roleTypes ,email, nickname, memo, createdBy);
    }

    public void addRoleType(RoleType roleType) {
        this.getRoleTypes().add(roleType);
    }

    public void addRoleTypes(Collection<RoleType> roleTypes){
        this.getRoleTypes().addAll(roleTypes);
    }

    public void removeRoleType(RoleType roleType) {
        this.getRoleTypes().remove(roleType);
    }

    @Override
    public boolean equals(Object object) {
        if (this == object) return true;
        if (!(object instanceof UserAccount that)) return false;
        return this.getUserId() != null && this.getUserId().equals(that.getUserId());
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.getUserId());
    }
}

 

권한 정보 몇개 때문에 테이블을 한개를 더 만드는게 부담스러워 비정규화 방법으로 collection 사용(RoleType)

 

@Convert(converter = RoleTypesConverter.class)

@Column(nullable = false)
private Set<RoleType> roleTypes = new LinkedHashSet<>();

 

RoleType Enum class

package com.fastcampus.projectboardadmin.domain.constant;

import lombok.Getter;
import lombok.RequiredArgsConstructor;


@RequiredArgsConstructor
public enum RoleType {
    USER("ROLE_USER"),
    MANGER("ROLE_MANAGER"),
    DEVELOPER("ROLE_DEVELOPER"),
    ADMIN("ROLE_ADMIN");

    @Getter
    private final String roleName;

}

 

BoardAdminPrincipal에 있는 RoleType enum 을 지우고 다시 생성

효과 => Spring Security 와의 결합이 줄어들어 권한 설정이 유연해지고 재사용 가능

 

RoleTypesConverter

package com.fastcampus.projectboardadmin.domain.converter;

import com.fastcampus.projectboardadmin.domain.constant.RoleType;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;

import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;

@Converter
public class RoleTypesConverter implements AttributeConverter<Set<RoleType> , String> {

    private static final String DELIMITER = ",";

    @Override
    public String convertToDatabaseColumn(Set<RoleType> attribute) {
        return attribute.stream().map(RoleType::name).sorted().collect(Collectors.joining(DELIMITER));
    }
    // "ADMIN,"USER" 이런 형식으로 return

    @Override
    public Set<RoleType> convertToEntityAttribute(String dbData) {
        return Arrays.stream(dbData.split(DELIMITER)).map(RoleType::valueOf)
                .collect(Collectors.toSet());
    }
    // Set.of(RoleType.ADMIN,  RoleType.USER) 형식으로 return

}

DB 에게 정보를 넘길때는 문자열로 보낼거라 이후 converter 사용

 

(1) convertToDatabaseColumn 을 통해 database 값으로 변환 

Set.of(RoleType.ADMIN , RoleType.User) ->"ADMIN, "USER"

(2) convertToEntityAttribute 을 통해 Entity 로 변환

"ADMIN, "USER" -> Set.of(RoleType.ADMIN , RoleType.User)

 

적용 : @converter(autoApply = true) 를 통해 모든 곳에 적용하거나 @Convert(converter = RoleTypesConverter.class)로 적용하고 싶은곳에 써준다.

 

BoardAdminPrincipal record

package com.fastcampus.projectboardadmin.dto.security;

import com.fastcampus.projectboardadmin.domain.constant.RoleType;
import com.fastcampus.projectboardadmin.dto.UserAccountDto;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public record BoardAdminPrincipal(
        String username ,
        String password,
        Collection<? extends GrantedAuthority> authorities,
        String email,
        String nickname ,
        String memo ,
        Map<String , Object> oAuth2Attributes)
        implements UserDetails , OAuth2User
{
    public static BoardAdminPrincipal of(String username, String password, Set<RoleType> roleTypes ,String email, String nickname, String memo) {
        return BoardAdminPrincipal.of(username, password, roleTypes, email, nickname, memo, Map.of());
    }

    public static BoardAdminPrincipal of(String username, String password, Set<RoleType> roleTypes ,String email, String nickname, String memo, Map<String, Object> oAuth2Attributes) {

        return new BoardAdminPrincipal(
                username,
                password,
                roleTypes.stream().map(RoleType::getRoleName)
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toUnmodifiableSet()),
                email,
                nickname,
                memo,
                oAuth2Attributes
        );
    }

    public static BoardAdminPrincipal from(UserAccountDto dto) {
        return BoardAdminPrincipal.of(
                dto.userId() ,
                dto.userPassword(),
                dto.roleTypes(),
                dto.email() ,
                dto.nickname(),
                dto.memo());
    }

    public UserAccountDto toDto(){
        return UserAccountDto.of(
                username,
                password,
                authorities.stream()
                        .map(GrantedAuthority::getAuthority)
                        .map(RoleType::valueOf)
                        .collect(Collectors.toUnmodifiableSet()),
                email,
                nickname,
                memo);
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }


    @Override
    public Map<String, Object> getAttributes() {
        return oAuth2Attributes;
    }
    @Override
    public String getName() {
        return username;
    }

}

 

boardAdminPrincipal record 에도 권한 필드에 RoleType Enum 적용

 

UserDetails 란? 

spring security 에서 사용자 정보를 담는 인터페이스이다.

 

게시판 프로젝트에서 했던 KakaoOAuth2Response 객체와 그에 대한 테스트 코드도 도메인에 추가하였다.

 

이번 도메인 설계를 통해 사용자 권한 설정을 중점적으로 배울 수 있었다.