Application Design

사례로 배워보는 디자인패턴 #1 - 기본적인 MVC

멋진그이름 2019. 10. 17. 17:05

<개요>

- 일반적인 Web MVC구조에 따라서 Service 등록/수정/삭제/조회 하는 REST API를 만든다고 가정합니다.

 

 

<내용>

가장 단순한 건당 조회를 살펴봅니다.

Controller 클래스 입니다.

@RestController
@RequestMapping(path="/api")
public class ServiceController {

    
    @RequestMapping(value="/services/{serviceId}", method= RequestMethod.GET)
    public @ResponseBody
    ServiceDto findService (@AuthenticationPrincipal LoginUserDetails loginUserDetails,
                            @PathVariable("serviceId") int serviceId) throws ServiceNotFoundException {
        // @ResponseBody means the returned String is the response, not a view name
        // @RequestParam means it is a parameter from the GET or POST request

        ServiceDto serviceDto = getServiceByServiceId(serviceId, loginUserDetails);
        return serviceDto;
    }

    private ServiceDto getServiceByServiceId(int serviceId, LoginUserDetails loginUserDetails) throws ServiceNotFoundException {
        ServiceDto serviceDto =  serviceService.findServiceById(serviceId);
        if(     (serviceDto == null) ||
                loginUserDetails.checkNotAvailableService( serviceDto.getServiceId()) ||
                StringUtils.isEmpty(serviceDto.getServiceId())){
            throw new ServiceNotFoundException(String.valueOf(serviceId));
        }
        return serviceDto;
    }

- LoginUserDetails 의 경우 로그인한 사용자의 정보를 저장합니다.
- serviceId를 이용하여 데이터를 조회하고 결과값을 간단하게 검증하는 로직입니다.

- 결과값이 다음 중 하나와 같으면 현재 Service가 존재하지 않는 것으로 판단합니다.

 1. 객체가 null 인 경우

 2. ID필드의 값이 없는 경우

 3. 값이 존재하나 로그인한 사용자의 정보에 해당하지 않는(본인의 서비스가 아닌 경우) 경우

 

이와 같이 Service 를 조회하고 값을 검증하는 로직은 조회 이외에 등록/수정에서도 필요하기 때문에 별도 메소드를 작성하여 재사용하였습니다.

 

그리고 현재 ServiceId와 로그인 사용자의 ServiceId를 비교하는 로직의 경우 Controller에서 구현하는 것보다는 정보은닉화와 설계원칙에 적합해 보여서 LoginUserDetails 내부에 구현하였습니다.

 

좀더 자세히 살펴보겠습니다.

public class LoginUserDetails extends User {

    private static String ROLE_ADMIN = "ROLE_ADMIN";

    private Integer userId;

    private Collection<Integer> services;

    public LoginUserDetails(Integer userId,
                            String password,
                            String userName,
                            Collection<? extends GrantedAuthority> authorities,
                            Collection<Integer> services){
        super(userName, password, authorities);
        this.services = services;
        this.userId = userId;
    
    
    public boolean checkNotAvailableService(Integer serviceId){

        if(this.getAuthorities().contains(new SimpleGrantedAuthority(ROLE_ADMIN)) ){
            return false;
        }

        for(Integer eachService : this.services){
            if(eachService.equals(serviceId)){
                return false;
            }
        }
        return true;
    }

이와 같이 구현하면 현재 사용자의 serviceId등은 외부로 노출시키지 않아도 되며 (getter를 작성하지 않아도 됩니다.)

값의 검증이 필요한 모듈은 LoginUserDetails 객체에 요청하기만 하면 됩니다.

 

Controller layer의 경우 web과 바로 연결되어 있는 부분들을 담당하기 때문에 login관련정보나 인자값을 주로 처리하며 비지니스 로직은 Service Layer에 존재하게 됩니다.

 

 

다음으로 Service Layer를 살펴보겠습니다.

@Service
public class ServiceService {
    @Autowired
    private ServiceRepository serviceRepository;

    @Autowired
    private ModelMapper modelMapper;

    public ServiceDto findServiceById(int id){
        ServiceEntity serviceEntity = serviceRepository.findById(id);
        return modelMapper.map(serviceEntity, ServiceDto.class);
    }

지금은 아무 로직이 없기 때문에 단순히 Repository 로부터 값을 조회하여 객체의 값을 매핑만 합니다.

 

 현재 프로젝트에서는 Spring JPA를 사용하여 시스템을 구축중인데, Entity클래스의 변경은 되도록 줄이는 것이 좋습니다. 만약 Entity 클래스 하나의 유형으로 화면 - 서비스 - 데이터를 모두 처리하게 될 경우 객체지향에서 말하는 대표적인 anti pattern이 될 수 있기 때문에 별도 DTO 클래스를 사용합니다.

 

@Data
@Getter
@Setter
public class ServiceDto {

    private Integer serviceId;

    private String serviceName;

    private String serviceCode;

    private ServiceType serviceType;

    private String description;

    private LocalDateTime creationDateTime;

    private LocalDateTime modificationDateTime;

    private Integer userId;

    private UserEntity user;

}

 

<정리>

- Layer는 일반적으로 자신과 연결되어 있는 부분에 대해서만 인터페이스 하는 것이 원칙입니다.

- Controller Layer에서는 실제 데이터 베이스 저장에 대해서 알 필요가 없으며

- Service Layer에서는 Web기술에 대해서 알 필요가 없고

- Repository Layer는 온전히 데이터의 저장만을 담당합니다.

 

다음시간에는 조금 더 자세한 케이스를 다뤄보겠습니다.