0. Table Of Content

 

 

 

 

1. 서론

배치 프로세스에 대한 공통 프레임워크를 담당하여 작업을 진행하고 있었다. Spring Batch Starter를 사용하여 작업을 진행하는 것이 가장 효율적이었지만,

기존 만들어진 테이블에 맞춰야 했기 때문에 해당 테이블에 맞게 배치 프레임워크를 새로 개발하게 되었다. 

프레임워크에서 build된 결과물인 jar파일은 crontab에 등록이 되어 jar 실행 시 입력받은 augument값으로 해당 batch job이 실행되어야 하기 때문에, 

입력 argument에 따라서 분기되는 프로세스가 필요하였다. 이에 해당하는 코드는 아래와 같다.

@Component
public class BatchArgumentListener implements ApplicationListener<ApplicationReadyEvent>{
 
    @Autowired
    private ApplicationArguments applicationArgument;
 
    @Autowired
    private ConfigurableApplicationContext context;
 
    @Autowired
    private DBEncKeyProperties dbProps;
 
    @Autowired
    private MemberActionLogService memberLogService;
 
    @Autowired
    private EdiDailyTxScheduler ediDailyTxScheduler;
 
    @Autowired
    private EdiDailySettleScheduler dailySettleScheduler;
 
    @Autowired
    private EdiMonthlyTxScheduler ediMonthlyTxScheduler;
 
    @Autowired
    private EdiVbankIncomeScheduler ediVbankIncomeScheduler;
 
    @Autowired
    private MemberDormantService memberDormantService;
 
    @Autowired
    private MemberStatisticsService memberStatisticsService;
 
    @Autowired
    private MemberUseService memberUseService;
 
 
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
 
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        String publicKey = this.argumentListValidator(applicationArgument, BatchArgumentConstant.ARG_PUBLIC_KEY);
        dbProps.setPublicKey(publicKey);
 
        String batchType = this.argumentListValidator(applicationArgument, BatchArgumentConstant.ARG_TYPE);
 
        String targetDate = this.dateValidator(applicationArgument);
 
        logger.info("batchType : " + batchType);
        logger.info("targetDate : " + targetDate);
 
        try {
            switch(batchType) {
                case BatchArgumentConstant.BATCH_MEMBER_ACTION:
                        memberLogService.memberActionTask(targetDate);
                        memberLogService.memberLastActionTask(targetDate);
                break;
 
                case BatchArgumentConstant.BATCH_DAILY_SETTLE:
                    dailySettleScheduler.getDailySettle();
                    break;
 
                case BatchArgumentConstant.BATCH_DAILY_TX:
                    ediDailyTxScheduler.getDailyTxNormal();
                    break;
 
                case BatchArgumentConstant.BATCH_MONTHLY_TX:
                    ediMonthlyTxScheduler.getMonthlyTxNormal();
                    break;
 
                case BatchArgumentConstant.BATCH_VBANK_INCOME:
                    ediVbankIncomeScheduler.getVbankIncomeDate();
                    break;
 
                case BatchArgumentConstant.BATCH_MEMBER_DORMANT:
                    memberDormantService.memberDormantMailTask(targetDate);
                    memberDormantService.memberDormantTask(targetDate);
                    break;
 
                case BatchArgumentConstant.BATCH_STATISTICS:
                    memberStatisticsService.memberStatisticsTask();
                    break;
                case BatchArgumentConstant.BATCH_MEMBER_USE:
                    memberUseService.memberUseMailTask();
                    break;
 
                default:
                    logger.info(BatchArgumentConstant.MESSAGE_INVALID_ARG);
                }
        }catch (Exception e) {
            logger.info("Process is not completed.");
        } finally {
            System.exit(SpringApplication.exit(context));
        }
    }
}

 

개인적인 회고시, 나는 내가 짠 프레임워크를 보고 참 더럽다고 생각이 들었으며 더많은 코드가 내가 작성한 프레임워크에 올라가기 전에 더러운 코드를 청소하고 싶었다.

우선 위 코드의 문제점을 정리하면 다음과 같았다.

  • 새로운 배치 프로세스가 프레임워크에 올라갈 때 마다 case문을 추가하여 분기시켜주는 귀찮음이 있다.
  • case문을 추가하기 위해서는 같은 파일에 여러명이 작업을 하게 되고, 그에 대한 결과로 git repository merge가 어려워진다.
  • static 변수가 프레임워크 내부에 많이 있었기 때문에 이를 조금이라도 줄이고 싶었다.
  • 여러개의 배치 프로세스가 모여 하나의 job형태로 이루어질 수 있었기 때문에 통합하여 관리하고 싶었다.

 

또한 작성된 모든 프로세스는 BatchJob이라는 공통점을 가지고 있었기 때문에, BatchJob이라는 키워드 하나로 통합할 수 있다고 생각을 하게 되었다.

 

위 문제점들을 해결하기 위해, Spring Design Pattern을 공부하다가 구글링을 통해 SpringFramework Guru에서 디자인 패턴에 관한 글을 보게 되었으며

디자인 패턴 중 프레임워크에 가장 적합한 Factory Method Design Pattern을 적용시켜보기로 하였다.

 

 

 

 

2. Factory Pattern이란 무엇인가

자바 어플리케이션을 보면 new 연산자를 이용하여 객체를 자주 생성한다. 작은 규모의 어플리케이션에서는 문제가 되는 것이 없지만, 큰규모의 코드를 작성하게 되면 Object의 개수가 자연스럽게 증가하며 Object들에 대한 관리 복잡도 역시 증가한다.

이러한 상황을 핸들링하기 위해서 팩토리 패턴을 적용할 수 있다. 이름에서 알 수 있듯이 펙토리 메소드 패턴은 펙토리 역할을 하는 객체를 생성하는 클래스를 사용한다.

팩토리 패턴은 직접 생성자를 이용하여 객체를 생성하는 대신 메소드를 통해 객체를 생성하는 것을 원칙으로 한다. 

펙토리 메소드 패턴에서 당신은 클래스를 생성하기 위해 Java interface 또는 abstract class 같은 인터페이스를 제공하여 인터페이스의 

인터페이스의 펙토리 메소드는 하나 이상의 서브클래스에 대한 오브젝트의 생성을 연기시킨다. 서브클래스는 어떤 객체가 만들어질지에 대해 선택하기 위해 팩토리를 implement하여야 한다.

 

 

 

2.1. Factory Parrern 예제 

글로만 이해하면 이해가 잘 가지 않으니, 코드로 표현하면서 이해를 해보자. 먼저 작성될 클래스 및 인터페이스 구조는 다음과 같다.

 

 

가장 처음 해야 할 일은 C++, Python, Java의 공통점을 찾는 것이다. 수많은 공통점 중 사용할 공통점은 다음과 같다.

  • 모두 프로그래밍 언어이다
  • 컴파일을 한다.
  • HTML은 프로그래밍 언어가 아닙니다.

 

위 요구사항을 담은 interface를 작성하면 다음과 같다.

Language.java

package com.jeonghyeong.sample;
 
public interface Language {
 
    public void compile();
 
    public String getLanguageType();
}

 

 

위 인터페이스를  생성한 다음, implement 받은 CPP, Java, Python 클래스를 구분해 줄 String 식별자를 담고 있는 class를 작성한다.

package com.jeonghyeong.sample;
 
public class LanguageType {
 
    public static final String CPP = "cpp";
 
    public static final String JAVA = "java";
 
    public static final String PYTHON = "python";
 
}

 

 

 

위 식별자를 이용하여 getLanguageType() 메소드에는 본인임을 나타내는 String을 반환하도록 하자.

C++, Java, Python에 대한 정의는 다음과 같다.

 

CPP.java

package com.jeonghyeong.sample;
 
public class CPP implements Language{
 
    @Override
    public void compile() {
        System.out.println("CPP Compile");
    }
 
    @Override
    public String getLanguageType() {
        return LanguageType.CPP;
    }
}

Java.java

package com.jeonghyeong.sample;
 
public class Java implements Language{
 
    @Override
    public void compile() {
        System.out.println("Java Compile");
    }
 
    @Override
    public String getLanguageType() {
        return LanguageType.JAVA;
    }
}

 

Python.java

package com.jeonghyeong.sample;
 
public class Python implements Language{
 
    @Override
    public void compile() {
        System.out.println("Python Compile");
    }
 
    @Override
    public String getLanguageType() {
        return LanguageType.PYTHON;
    }
}




간단하게 Language에 대한 여러개의 파생 클래스를 생성할 수 있었다. 위와 같은 패턴으로 일단 interface를 통해 각기 다른 언어들의 Object를 Language의 개념으로 묶을 수 있었다.


그런데, 이렇게 설계 해놓고, 객체를 생성할 때, new Python()이런 식으로 객체를 생성하면 논리적으로만 Language으로 묶은것과 다름이 없다. 파라미터에 따라 상황에 맞는 객체를 받아올 수 있도록 Factory클래스를 작성해보자.

LanguageFactory

package com.jeonghyeong.sample;
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
 
 
@Component
public class LanguageFactory {
 
    
    public Language getLanguage(String languageType) {
        if(languageType.equal(LanguageType.JAVA)){
            return new Java();
        }else if(languageType.equal(LanguageType.Python)){
            return new Python();
        }else if(languageType.equal(LanguageType.CPP)){
            return new CPP();
        }else
            return null;
    }
 
 
}

 

위와 같이 LanguageFactory를 이용해 getLanguage 메소드를 이용해 상황에 맞는 Language를 상속받은 객체를 가져올 수 있게 되었다.

 

그러나 위 코드 역시 if - else if - else문을 사용하기 때문에, Language interface를 상속받은 class가 생겨날 때 마다 수작업으로 else if를 이용해 코드를 작성해주어야 한다.

 

위 코드를 개선하는 방법으로 나는 Spring의 Bean을 생각하게 되었다.

 

위 Language interface를 상속받은 클래스를 모두 @Component annotation을 이용하여 Bean을 만들게 되면, @Autowired를 이용하여 Language를 상속받은 모든 Bean을 가져올 수 있게 된다.

 

개선된 Factory코드는 다음과 같다.

 

package com.jeonghyeong.sample;
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
 
 
public class LanguageFactory {
 
    @Autowired
    private List<Language> languageList;
 
    public Language getLanguage(String languageType) {
        for(Language language : languageList) {
            if(languageType.equals(language.getLanguageType()))
                return language;
        }
        return null;
    }
 
}

 

 

이제 Factory를 이용하여 메소드를 이용하여 상황에 맞는 객체를 얻어보자. 간단하게 ApplicationListener를 상속받은 클래스를 하나 생성하여 테스트해보았다.

package com.jeonghyeong.sample;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;
 
@Service
public class TestFactory  implements ApplicationListener<ApplicationReadyEvent>{
 
    @Autowired
    private LanguageFactory factory;
 
 
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        Language cpp = factory.getLanguage(LanguageType.CPP);
        Language java = factory.getLanguage(LanguageType.JAVA);
        Language python = factory.getLanguage(LanguageType.PYTHON);
 
        cpp.compile();
        System.out.println(cpp.getClass());
 
        java.compile();
        System.out.println(java.getClass());
 
        python.compile();
        System.out.println(python.getClass());
 
    }
 
}

 

위 코드의 결과는 다음과 같으며, 정상적으로 잘 받아오는 것을 확인하였다.

 

 

 

 

3. Factory Pattern 사용으로 얻을 수 있는 기대 효과

  • 비슷한 성격의 객체를 인터페이스를 통해 하나로 관리할 수 있다.
  • 어떤 유형의 객체인지 판별하는 if-else문이 줄어들기 때문에 코드의 가독성이 증가한다.
  • 협업시, 공통코드를 건드리는 일이 없이 업무를 진행할 수 있기 때문에 효율성이 증가한다.
  • 추후 비슷한 유형의 객체가 새로 생성되어도 implement를 통해 쉽게 추가할 수 있다.

 

 

 

 

 

4. 실무에서 적용한 코드 일부

 

서론에 switch문으로 분기하던 더러운 코드를 다음과 같이 개선하여 가독성을 높였으며, 더이상 이 부분에 대해서 작업이 없어지게 되어 코드 관리가 용이해졌다.

또한, 무수히 많은 autowired가 사라졌으며 batchJob에 대한 로직을 각 job 패키지별로 따로 관리하게 되었다. 

개선된 BatchAgumentListener

@Component
public class BatchArgumentListener implements ApplicationListener<ApplicationReadyEvent>{
 
    @Autowired
    private ApplicationArguments applicationArgument;
 
    @Autowired
    private ConfigurableApplicationContext context;
 
    @Autowired
    private DBEncKeyProperties dbProps;
 
 
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
 
    @Autowired
    private BatchJobFactory batchJobFactory;
 
 
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
 
 
 
 
        String batchType = this.argumentListValidator(applicationArgument, BatchArgumentConstant.ARG_TYPE);
 
 
        logger.info("batchType : " + batchType);
 
 
        /*
         * DB암호화키를 얻기 위한 public key를 ApplicationArgument에서 가져온다
         */
        if(applicationArgument.containsOption(BatchArgumentConstant.ARG_PUBLIC_KEY)){
            String publicKey = applicationArgument.getOptionValues(BatchArgumentConstant.ARG_PUBLIC_KEY).get(0);
            dbProps.setPublicKey(publicKey);
        }
 
 
        /*
         * ApplicationArgument에서 type parameter의 value값을 가져온다.
         */
        if(applicationArgument.containsOption(BatchArgumentConstant.ARG_TYPE)) {
            batchType = applicationArgument.getOptionValues(BatchArgumentConstant.ARG_TYPE).get(0);
        }else {
            logger.info("Type parameter should be required.");
            System.exit(SpringApplication.exit(context));
        }
 
 
        /*
         * 가져온 type에 맞는 batchJob을 생성하여 프로세스를 실행시킨다.
         */
        try {
            BatchJob batchJob = batchJobFactory.getService(batchType);
            logger.info(batchJob.getBatchType());
            if(batchJob!=null) {
                batchJob.run();
            }
 
 
        }catch (Exception e) {
            logger.info(e.getMessage());
        }
 
 
    }
 
 
    /*
     * argument가 null인 상태에 대해 예외처리하는 메소드
     */
    private String argumentListValidator(ApplicationArguments arg, String argumentType) {
 
        String argValue = null;
        try {
            argValue = arg.getOptionValues(argumentType).get(0);
            return argValue;
        }catch (Exception e) {
            logger.info(BatchArgumentConstant.MESSAGE_NO_ARG_TYPE);
            System.exit(SpringApplication.exit(context));
        }
 
        return argValue;
    }
}

 

패키지별로 흩어져 있던 배치 job을 interface와 factory 패턴을 적용하여 관리가 용이하게 하였다. 

BatchJob

public interface BatchJob {
 
    public void run();
 
    public String getBatchType();
}

BatchJobFactory

@Component
public class BatchJobFactory {
 
    @Autowired
    private List<BatchJob> batchJobList;
 
 
 
    public BatchJob getService(String batchType) {
        for(BatchJob job : batchJobList) {
            if(batchType.equals(job.getBatchType()))
                return job;
        }
 
        return null;
    }
}

 

 

기존 BatchArgumentListener에서 볼 수 있었던 배치 job의 flow를 각 역할에 맞는 패키지의 implement된 BatchJob을 implement받은 객체에 작성함으로써 코드의 가독성과 관리의 용이성이 증가하였다.

또한 BatchArgumentListener에서 작업을 하지 않아도 되므로 여러사람이 협업시 git repo가 confilct날 일이 줄어들었다.

@BatchProcess
public class MemberActionLogJob implements BatchJob{
 
    @Autowired
    private MemberActionCollectService memberActionCollectService;
 
    @Autowired
    private MemberLastActionService memberLastActionService;
 
 
    @Override
    public void run() {
        /*
         * service 패키지에서 작성한 하나 이상의 프로세스가 올 수 있다.
         */
        memberActionCollectService.startBatch();
        memberLastActionService.startBatch();
    }
 
    @Override
    public String getBatchType() {
        /*
         * command line으로 받는 배치 type을 반환시켜준다.
         * BatchArgumentConstant에서 이를 추가 해주어야 한다.
         */
        return BatchArgumentConstant.BATCH_MEMBER_ACTION;
    }
}

 

 

 

 

5. 후기

처음 작성한 프레임워크인 만큼 열심히 하였으나, 여기저기서 git conflict가 발생하였고 코드가 너무 가독성이 떨어지며 쓰기 어렵다고 주변에서 컴플레인이 들어왔다.

이미 프레임워크에 여러 사람이 코드를 작성하고 있었고, 향후 더 많은 코드가 작성될 예정이라고 하였다.

먼저 코드를 작성한 분들께는 죄송하지만, 다음 작업을 더 편하게 하기 위해 프레임워크 개선을 진행하였고 관리 및 개인 업무가 더욱 용이하도록 factory 패턴을 적용하여 많은 개선을 할 수 있었으며 코드를 추가하기 쉬워졌다고 피드백을 받았다.

다음 프레임워크 또는 코드를 작성하기 전에 이러한 디자인 패턴을 적용 할 수 있는지도 고려를 해야겠다.

함께 일하는 직업인 만큼 모두가 웃을 수 있는 업무를 위해 많이 노력해야겠다.

 

1. 상황

휴면회원에 대한 기록을 쌓는 배치 프로세스에 대하여 쿼리를 작성 중이었다. 프로세스 흐름도는 다음과 같다.

 

 

2. 문제점

    - 연관성이 없는 각 select에 대해 단일 스레드를 통해서 순차적으로 쿼리를 실행하였기 때문에, 현재 상황보다 더 많은 테이블에 대해 작 

       업을 할 경우, 많은 시간이 소모된다.

    - @Transactional이 동작하는 사이클에 대해서 제대로 알고 쓰지 못하였기 때문에, rollback이 제대로 되지 않고 있다.

    - Spring Boot Batch Starter가 있음에도 불구하고 일반 절차 함수를 작성하듯이 프로그램을 작성하였다.

    - 후에 내가 작성한 프레임워크에 다른 배치 프로세스가 추가적으로 작성하는 것을 잘 몰랐기 때문에, 확장성이 덜 고려된 공통 코드를            작성하였다.

    - 작성한 프레임워크는 jar파일로 build되어 

   

 

 

 

3. 개선(해야할)점

    - 상호 관련이 없고, 연계성이 없는 쿼리의 경우는 단일 스레드에서 작업하지 않고, 복수개의 스레드에서 작업하여 속도를 향상시킨다.

    - 자체 코드 리뷰 결과, @Transactional을 사용한 @Service 내부의 method에서 catch문에서 exception을 던지고 있지 않기 때문에, 

       Spring에서는 exception을 감지하지 못하고, rollback를 시키지 않는 점이 발견되었다. 각 catch 문에 대해서 마지막에 exception을

       던져서 rollback이 가능하게 한다.

    - AOP에서 배치프로세스에 대한 로그를 작성할 때, @Transactional 어노테이션 걸린 서비스에 대해 동작하도록 되어있는데, 이를 조금 

      더 적절한 방법으로 대체할 수 있으면 대체를 하는 것이 좋다.

    - 불필요하게 Embadded Tomcat과 서버가 실행되는 것을 disable하여 jar 파일이 실행되기 전 준비단계를 줄였다.

    - JVM이 running 되기 전 단계에서 프로세스를 처리하도록 ApplicationListener를 이용하여 ContextRefreshedEvent에 대해 핸들링   

       하였다. 그러나, ContextRefreshedEvent의 경우는 Context가 바뀌는 상황마다 ApplicationListener가 호출되므로, spring이 시작할 

       때, 단발성으로 호출되도록 ApplicationStartingEvent같은 것을 사용하는 것이 더 좋을 듯하다.

    - 실행파일 형태로 cron에 걸수 있도록 설계하라하여 지금 상황과 같이 설계하였다. 그러나, 현행방법과 데몬이 계속 떠있는 방식 중 어   

       떤것이 더 효율적일까를 따져봐야 할 것이다.

'회고' 카테고리의 다른 글

20200624 회고 - 배치 프로세스 및 데이터베이스  (0) 2020.07.28
2019 회고  (2) 2020.02.01
20191126 회고 - 프로그래밍 패턴  (0) 2019.11.26

1. Create

  • URL : <domain>/<version>/user
  • Method : POST
  • Request Header
    • Content-Type: application/json
  • Request Body (format = JsonObject)
    • Userinfo(userid, username, password, email, phone_number, address,  ... ) - JsonObject
    • secretHash HMAC(signing_key(clientSecret, sha256)[clientId + username]) - String
  • Response

    • If success, 
    • If failed, response is HTTP status code with customed error message

 

2. Read

  • URL : <domain>/<version>/user/me
  • Method : GET or POST
  • Request Header :
    • Authorization: Bearer <YOUR_ACCESS_TOKEN>
  • Response
    • If success, response is HTTP status code with userinfo that matched with access token.
    • If failed, response is HTTP status code with customed error message.

2.1. Read by List

  • URL : <domain>/<version>/users
  • Method : GET or POST
  • Request Header : 
    • Authorization: Bearer <YOUR_ACCESS_TOKEN>
    • Content-Type: application/json
  • Request Body
    • Query Parameters - JsonObject

 

 

 

3. Update(덮어쓰기? 부분업데이트?)

  • URL : <domain>/<version>/user/me
  • Method : PUT
  • Request Header
    • Authorization: Bearer <YOUR_ACCESS_TOKEN>
    • Content-Type: application/json
  • Request Body
    • Updated UserInfo - JsonObject
  • Response
    • If Success, return requester's id
    • If failed, response is HTTP status code with customed error message.

 

 

 

4. Delete

  • URL : <domain>/<version>/user/me
  • Method : DELETE
  • Request Header
    • Authorization: Bearer <YOUR_ACCESS_TOKEN>
  • Response
    • If Success, return customed message that user delete is completed.
    • If failed, return HTTP status code with customed error message.

 

 

 

 

 

5. User Table

  • DB : mysql
  • Storage Engine : InnoDB (Transaction이 많이 일어나기 때문에 MyISAM보다 InnoDB가 유리)
  • Schema : portal_user 
  • 향후 잘못되었다 판단한 부분은 빨간색으로 표시

 

Column Type Nullable PK 설명
id varchar(255)    O 사용자 고유 식별자
name varchar(50)     사용자 이름
email varchar(100)     사용자 이메일
authorities varchar(255) → ????     권한 → 역할의 조합이므로, 테이블을 따로 빼서 관리한다.
password varchar(255)     암호화된 비밀번호
role varchar(30) → ????     역할 → 권한에 종속되는 개념이므로, 정규화가 필요함.
last_logined_date datetime     마지막 로그인된 날짜 및 시간
account_create_date datetime     계정 생성 날짜
account_last_update_date datetime     마지막 계정 정보 수정 날짜 → 로그성임. 
credential_last_update_date datetime     비밀번호 마지막 변경 날짜 → 바로 밑 항목과 비슷한 성격이다.
비밀번호 만료일을 가지고 계산할 수 있는 항목임.
credential_expire_date datetime     비밀번호 만료일 → last update를 이 컬럼으로 대체 할 수 있음.
is_account_enabled bit(1)     계정 활성화 여부
is_account_locked bit(1)     계정 잠금 여부
is_credential_expired bit(1)     비밀번호 만료 여부 → 이 컬럼이 필요한가?
is_account_expired bit(1)     계정 만료 여부 (spring security isEnabled()와 매칭되는 column)

 

 

 

5.1. DB Table 설계시, 참고자료

https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/core/userdetails/UserDetails.java

 

 

5.2. 찾은 문제점

  • Spring Security의 Default UserDetails를 보고 필드를 설계를 하였으나, 설계한 필드들이 "왜"쓰이는 필드들인지 이해를 하지 못하였다.
  • 오버헤드가 걸리지 않는 선에서 충분히 credential expire date에 대해 판별할 수 있지만, credential_expire_date와 is_credential_expired와 같은 중복케이스가 첫 설계시 존재했다.
  • 데이터에 비해 불필요하게 크기를 잡았다. 불필요한 데이터 크기 설계는 자제하도록 하자.
  • mysql 데이터 타입을 잘 모르다 보니 효율적인 데이터 타입에 대한 선택을 하지 못했다.
  • 정규화를 하지 못했다.

 

 

 

6. 20200423 개선된 DB 설계

 

6.1. User Schema

Column Type Nullable PK 설명
id varchar(20)    O 사용자 고유 식별자
name varchar(10)     사용자 이름
email varchar(30)     사용자 이메일
password varchar(255)     암호화된 비밀번호
last_logined_date datetime O   마지막 로그인된 날짜 및 시간
account_create_date datetime     계정 생성 날짜
account_last_update_date datetime     마지막 계정 정보 수정 날짜
credential_expire_date datetime     비밀번호 만료일
is_account_enabled bit(1)     계정 활성화 여부
is_account_locked bit(1)     계정 잠금 여부
is_account_expired bit(1)     계정 만료 여부

 

 

6.2. Authority Schema(복합 PK)

Column Type Nullable PK FK 설명
id varchar(20)   O User.id User Table로부터의 외래키
role varchar(10)   O   User에게 부여된 역할

+ Recent posts