그동안 Node.js로만 개발을 해보았기 때문에 최근들어서는 Java를 이용해 백엔드를 구성해보는 시도를 하고있다.
아무래도 처음 접해보는 언어이고 프레임워크이기 때문에 '클라이언트의 요청을 처리해 반환한다'는 사실 외에 큰 차이가 있었고 이 부분에 적응하기 쉽지 않았다.
일단 가장 처음으로 겪은 차이점은, 어노테이션을 사용한 매핑 방식이었다.
Node에서는 라우팅과 미들웨어로 요청을 처리하는데, Java에서는 전혀 다른 방식이다.
app.get, app.post와 같이 라우트 메서드를 통해 처리하는 방식과 달리,
@GetMapping, @PostMapping 과 같은 어노테이션이 메서드를 정의하거나 어떤 역할을 하는지를 명시함으로 처리할 수 있다.
간단한 방식에 적잖이 놀라웠는데, 특히나 데이터베이스 작업을 처리하는 방법이 인상깊었다.
Node에서는 요청을 받으면 쿼리문을 이용해 데이터를 처리했다.
그런데 Java에서는 JPA 등의 기술을 사용하여 간단히 처리한다.
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
List<Post> findAll();
Post save(Post post);
}
데이터베이스와 상호작용하기위한 인터페이스의 예시이다.
이 인터페이스 하나면 'SELECT * FROM posts'와 같은 쿼리문이 필요하지 않게 된다.
인터페이스에서 CRUD 기능을 모두 제공하므로 사용하는 컨트롤러에서 postRepository.findAll(); 이런식으로 간단히 가져다 쓸 수 있다.
마치 거스름돈을 일일히 세서 건네주다가 기계가 알아서 건네주는 자동화와 같아진 것이다.
그러니까, Node에서는 직접적으로 하던 일이 Java에서는 뭔가 추상적으로 변한 것이다.
이러한 이유 때문에 자바의 계층 구조도 특이하게 다가왔다.
컨트롤러 - 서비스 - 레포지토리 - 도메인 이렇게 4단계의 계층으로 이루어진 레이어드 아키텍처를 따르는데, 적어도 Node에서는 이런 계층이 강제되지는 않았다.
물론 나름대로 MVC패턴에 맞추어 작업하기도 하지만 개인적으로 규모가 큰 프로젝트등은 다루지 않기에 일일이 다 나누어서 하지는 않았다.
일단 설명부터 해보자면,
- 컨트롤러 계층 : HTTP 요청을 받아 서비스 계층에서 전달하고 클라이언트에게 반환하는 입출력 계층
- 서비스 계층 : 컨트롤러에서 받은 요청 처리, 주로 비즈니스 로직을 처리하고 데이터베이스(레포지토리 계층)과 상호작용
- 레포지토리 계층 : 데이터베이스와 직접적 소통. CRUD
- 도메인 계층 : 엔티티 정의 계층. 데이터베이스 스키마 등
이렇게 구성되어있다.
아까 언급했듯 개인적으로 자바의 추상적인 방식 때문에, 이 계층 구조가 납득이 되지 않았다.
레포지토리 계층은 하나의 메서드처럼 구성되어있기 때문에 그렇다쳐도, 간단한 수준에서 본 컨트롤러 계층과 서비스 계층은 굳이 나누어야하나 싶었다.
@RestController
@RequestMapping("/posts")
public class PostController {
private final PostService postService;
public PostController(PostService postService) {
this.postService = postService;
}
@GetMapping
public List<Post> getAllPosts() {
return postService.getAllPosts();
}
@PostMapping
public ResponseEntity<Post> createPost(@RequestBody Post post) {
Post savedPost = postService.createPost(post);
return ResponseEntity.ok(savedPost);
}
}
@Service
public class PostService {
private final PostRepository postRepository;
public PostService(PostRepository postRepository) {
this.postRepository = postRepository;
}
public List<Post> getAllPosts() {
return postRepository.findAll();
}
public Post createPost(Post post) {
return postRepository.save(post);
}
}
각각 컨트롤러 파일과 서비스 파일이다.
언뜻 보기에도 구조가 유사하고, 처음에는 굳이 두 번 호출해서 쓸 것이 아니라 컨트롤러 단계에서 바로 레포지토리를 호출해서 쓰면 간결할거 같은데, 나누어둔 방식이 납득이 되지 않았다.
아직 복잡한 프로젝트를 해보지 않아서 모르겠으나, 일단은 서비스 계층에서 데이터 가공이나 트랜잭션 등을 처리하기 때문에 코드의 가독성이나 유지보수에 이점이 있다고 한다.
여튼, Java에서는 라우팅을 통해 연결해서 직접적으로 처리하던 Node와는 달리 정해진 구조에 따라 매핑 방식을 통해 처리하는 차이를 보이고 있다.
아직 계속 공부해봐야겠지만, 나름대로 이해해보니 방식에 차이가 있을 뿐 의도는 같았다.