Java

[어드민 페이지 만들기] 서비스 로직 개발_API 만들기 (Create, Read)

15호의 개발자 2022. 3. 21. 12:00
반응형

[어드민 페이지 만들기] 서비스 로직 개발_API 만들기 (Create, Read)

 


 

 

이제는 service를 만들어야 한다. 서비스는 어드민 프로젝트의 서비스 로직을 담당하는 부분으로써, 어떤 데이터를 만들거나 수정하는 부분을 담당할 것이다.

 

 

 

UserApiLogicService

@Service
public class UserApiLogicService implements CrudInterface<UserApiRequest, UserApiResponse> {

    @Autowired
    private UserRepository userRepository;

    // 1. request data 가져오기
    // 2. user 생성하기
    // 3. 생성된 데이터 기준으로 UserApiResponse 만들어서 return하기
    @Override
    public Header<UserApiResponse> create(Header<UserApiRequest> request) {
        return null;
    }

    @Override
    public Header<UserApiResponse> read(Long id) {
        return null;
    }

    @Override
    public Header<UserApiResponse> update(Header<UserApiRequest> request) {
        return null;
    }

    @Override
    public Header delete(Long id) {
        return null;
    }
    
}

 

service 패키지를 하나 만든 후 하위에 UserApiLogicService를 생성한다. UserApiLogicService는 앞에서 만든 CrudInterface를 상속받고서, 자동 override 기능을 이용해 CRUD 메서드를 오버라이드한다. 

 

그리고 제일 중요한 repository를 import 시킨다. 원래는 repository 또한 다른 서비스로 분리시켜야 한다. 이는 나중에 하기로 하고 지금은 기본적인 코딩 위주로 작업한다. CRUD 메서드를 하나씩 완성해보자.

 

 


 

 

1. Create

 

create 메서드가 할 작업 순서는 다음과 같다.

 

1. request data를 가져온다.

2. user를 생성한다.

3. 생성된 데이터를 기준으로 UserApiResponse를 만들어서 return한다.

 

이 작업이 끝나면 해당 create 메서드가 할 일은 끝난다. 이제 데이터를 가져와보도록 하자.

 

 

 

UserApiLogicService

@Service
public class UserApiLogicService implements CrudInterface<UserApiRequest, UserApiResponse> {

    // 1. request data 가져오기
    // 2. user 생성하기
    // 3. 생성된 데이터 기준으로 UserApiResponse 만들어서 return하기
    @Override
    public Header<UserApiResponse> create(Header<UserApiRequest> request) {

        // 1. request data 가져오기
        UserApiRequest userApiRequest = request.getData();

        // 2. user 생성하기
        User user = User.builder()
                .account(userApiRequest.getAccount())
                .password(userApiRequest.getPassword())
                .status("REGISTERED")
                .phoneNumber(userApiRequest.getPhoneNumber())
                .email(userApiRequest.getEmail())
                .registeredAt(LocalDateTime.now())
                .build();

        User newUser = userRepository.save(user);

        // 3. 생성된 데이터 기준으로 UserApiResponse 만들어서 return하기
        return Header.OK(response(newUser));
        
    }
    
    private UserApiResponse response(User user) {
    
        // user 객체를 가지고 userApiResponse를 return해주는 함수
        UserApiResponse userApiResponse = UserApiResponse.builder()
                .id(user.getId())
                .account(user.getAccount())
                .password(user.getPassword())   // todo 암호화, 길이를 return한다든지 등의 옵션이 생길 수 있음
                .email(user.getEmail())
                .phoneNumber(user.getPhoneNumber())
                .status(user.getStatus())
                .registeredAt(user.getRegisteredAt())
                .unregisteredAt(user.getUnregisteredAt())
                .build();

        // Header에 data 부분을 합쳐서 return하기
        return Header.OK(response(newUser));
        
    }
    
}

 

우선 UserApiRequest에서 request data를 getDate() 메서드를 통해 가져오고, user를 생성한 후 빌더 메서드를 통해 값을 넣어 준다. 그리고 이를 save 메서드를 통해 새로운 user에 저장한다.

 

그리고 3번의 response를 만들어주는 경우, 이는 user 객체로 만들어서 리턴하는 값이기 때문에 create 외에도 read나 update 등 다른 데에서도 사용하는 부분이다. 자주 사용하는 부분이므로 중복을 피하기 위해 response 함수로 따로 빼서 만든다. 이 response 함수는 user 객체를 가지고 userApiResponse를 리턴해주는 함수이다.

 

 

 

UserApiController

@Slf4j
@RestController
@RequestMapping("/api/user")
public class UserApiController implements CrudInterface<UserApiRequest, UserApiResponse> {

    @Autowired
    private UserApiLogicService userApiLogicService;
    
    @Override
    @PostMapping("")    // /api/user
    public Header<UserApiResponse> create(@RequestBody Header<UserApiRequest> request) {
        log.info("{}", request);
        return userApiLogicService.create(request);
    }
    
}

 

우선 create는 당연히 service와 연결시켜줘야하기 때문에 UserApiLogicService를 가져온다. 그리고 create 메서드와 매개변수로 넘어온 request와 연결을 시켜주면 user.ApiLogicService의 create에 가서 User를 생성한 후, 생성된 데이터를 바탕으로 useApiResponse를 만들어서 return 해준다.

 

실제로 개발할 때는 이런 식으로 서비스 로직이 모두 개발이 완성되면, 테스트 코드를 통해 테스트를 해봐야한다. 여기서는 이 과정은 생략하고, Rest API 툴을 통해서 잘 동작하는지 확인하도록 하겠다. Talend API Tester를 이용한다. (참고)

 

 

create이니까 POST 메서드를 선택하고, 위의 주소를 입력한다. https가 아니고 http로 입력해야 한다. BODY 부분에는 위와 같이 샘플 데이터를 넣어둔다.

 

 

send를 보내면 위에서 api 문서대로 정의한 헤더 부분과 데이터 부분이 정상적으로 뜨는 것을 확인할 수도 있다.

 

 

그리고 로깅 시스템을 활용해서 request가 정상적으로 들어오는지 로그를 통해 확인해보자. 콘솔을 확인해보면 request에 해당하는 부분이 toString으로 잘 찍혀있는 것을 확인할 수 있을 것이다.

 

 

 

@Slf4j 로깅 시스템이란?

 

 

개발하면서 System.out.prinln()을 통해 데이터가 어떻게 이동하는지를 콘솔창에 찍어본 경험이 있을 것이다. 이는 디버깅하기 가장 쉬운 방법이긴 하지만 실질적인 서비스에서는 이렇게 콘솔창을 보는 게 아니라, 어떠한 파일에 로깅 또는 로깅 시스템을 통해 로그를 남기는 방법을 더 많이 사용한다. 롬복(lombok)에 @Slf4j라는 어노테이션이 있는데, log.info() 등을 통해 info 레벨로 로그를 남겨서 확인할 수 있다. 이는 기본적으로 System.out.prinln()과 유사하게 동작한다. 차이점은, 나중에 로그 파일에 로그 서비스를 추가하겠다고 하면 로그 레벨을 선택할 수 있다는 점이다. 일단은 자바에서 심플하게 쓰는 로깅 시스템이라고 생각하기로 하자.

 

log.info()의 사용 방법은 다음과 같다.

 

log.info("{}, {}", request, "ABC");

 

첫 번째 파라미터 속 중괄호({})는 뒤에 따라오는 파라미터 수와 일치해야하며, 각 파라미터들이 순서대로 중괄호 안에 매칭되어서 찍힌다. 위의 log.info의 경우 아래와 같이 파일에 로그가 찍힐 것이다.

 

request.tostring(), ABC

 

 


 

2. Read

 

Read는 굉장히 간단하다. api http 메서드 방식은 get 방식이고, 주소에 들어있는 path variable에 해당하는 id를 database에서 select해서 response를 내려주면 된다. 단, 전제조건은 클라이언트가 id값을 반드시 알아야 한다는 점이다. 그러기 위해서는 전체 목록 조회와 같은 다른 api를 통해서 목록을 내려받은 후에, 상세한 정보를 알고싶을 때 id를 붙여서 보내야 한다. 그리고 나서 그 id에 해당되는 데이터를 내려줄 예정이다. 즉, response는 UserApiResponse와 계속 동일한 형태이고 request만 바뀐 형태이다.

 

 

 

UserApiController

@Slf4j
@RestController
@RequestMapping("/api/user")
public class UserApiController implements CrudInterface<UserApiRequest, UserApiResponse> {

    @Autowired
    private UserApiLogicService userApiLogicService;
    
    // create 생략. 위 참고
    
    @Override
    @GetMapping("{id}") // /api/user/{id}
    public Header<UserApiResponse> read(@PathVariable(name = "id") Long id) {
        log.info("read id: {}", id);
        return userApiLogicService.read(id);
    }
    
}

 

id에 데이터가 어떤 식으로 들어가있는지 log.info로 찍어보고, userApiLogicService에 read로 id를 연결시켜 준다. 

 

 

 

UserApiLogicService

@Service
public class UserApiLogicService implements CrudInterface<UserApiRequest, UserApiResponse> {

    // create 생략. 위 참고
    
    @Override
    public Header<UserApiResponse> read(Long id) {

        // id를 가지고 repository를 통해서 getOne 또는 getById를 통해 데이터를 가져옴
        Optional<User> optional = userRepository.findById(id);

        // 해당 데이터 user가 오면 userApiResponse를 return
        return optional  // optional이 return되면서 user가 있는 경우/없는 경우로 나눔
                .map( user -> response(user) )    // user가 있는 경우
                .map( userApiResponse -> Header.OK(userApiResponse) )
                .orElseGet( () -> Header.ERROR("데이터 없음") );   // user가 없는 경우: Header에 ERROR를 넘기면서 "데이터 없음"을 return

    }
    
    private UserApiResponse response(User user) {
    
        // user 객체를 가지고 userApiResponse를 return해주는 함수
        UserApiResponse userApiResponse = UserApiResponse.builder()
                .id(user.getId())
                .account(user.getAccount())
                .password(user.getPassword())   // todo 암호화, 길이를 return한다든지 등의 옵션이 생길 수 있음
                .email(user.getEmail())
                .phoneNumber(user.getPhoneNumber())
                .status(user.getStatus())
                .registeredAt(user.getRegisteredAt())
                .unregisteredAt(user.getUnregisteredAt())
                .build();

        // Header에 data 부분을 합쳐서 return하기
        return Header.OK(response(newUser));
        
    }
    
}

 

이제 서비스에서 read를 작성해보자. 우선 id를 가지고 repository를 통해서 getOne 또는 getById를 통해서 데이터를 가져온다. 그리고 해당 데이터 user가 오면 이를 가지고 userApiResponse 데이터를 return해주면 된다.

 

optional로 user를 받아올 때, user가 있으면 map을 탈 것이다. map이라는 것은 다른 return 형태로 바꾸는 것이다. 위의 map( user -> response(user) )의 경우 user를 받아 response 함수를 타면서 Header의 UserApiResponse로 return 할 것이다.

 

 

 

또는 read 부분을 아래와 같이 람다식으로 한 번에 연결할 수도 있다.

@Override
public Header<UserApiResponse> read(Long id) {

    // id를 가지고 repository를 통해서 getOne 또는 getById를 통해 데이터를 가져옴
    // 해당 데이터 user가 오면 userApiResponse를 return
    return userRepository.findById(id)  // optional이 return되면서 user가 있는 경우/없는 경우로 나눔
            .map( user -> response(user) )    // user가 있는 경우
            .map( userApiResponse -> Header.OK(userApiResponse) )
            .orElseGet( () -> Header.ERROR("데이터 없음") );   // user가 없는 경우: Header에 ERROR를 넘기면서 "데이터 없음"을 return

}

 

 

 

 

컨트롤러와 read가 연결되었기 때문에 프로젝트를 다시 실행시킨다. 먼저 데이터가 잘 내려오는지 rest client 툴을 이용해 확인해보자.

 

 

api/user/ 의 path parameter에 1번을 호출해보자. send를 누르면 아래와 같이 데이터가 잘 내려오는 것을 확인할 수 있다.

 

 

 

 

만약 아래와 같이 path parameter로 100번과 같이 존재하지 않는 번호를 호출하면, result_code는 "ERROR"로 뜨며 "데이터 없음"이라고 뜨는 것도 확인할 수 있다.

 

 

 


 

 

 

(출처: 패스트캠퍼스 Java & SpringBoot로 시작하는 웹 프로그래밍)

 

 

 

반응형