이전 글에서는 Spring Boot에서 코드를 역할별로 나누는 이유를 정리했습니다.
크게 보면 이런 구조였습니다.
Controller = 요청 받기
Service = 규칙 처리하기
Repository = 데이터 다루기
이번 글에서는 이 구조를 실제 코드로 연결해보겠습니다.
아주 간단한 회원 등록 예제를 만들면서 백엔드에서 요청이 들어오고, 저장되고, 다시 조회되는 흐름을 확인해보겠습니다.
이번 글의 목표는 딱 하나입니다.
간단한 백엔드 흐름 체험하기
오늘 만들 기능
이번 글에서는 아래 기능을 만들어보겠습니다.
- 회원 객체 만들기
- 회원 이름 저장하기
- 저장된 회원 목록 조회하기
- DB 대신 메모리 저장소 사용하기
아직 데이터베이스는 사용하지 않습니다.
처음부터 DB까지 연결하면 헷갈릴 수 있기 때문에, 이번에는 프로그램 안의 메모리에 임시로 저장해보겠습니다.
전체 흐름 먼저 보기
회원 등록 흐름은 이렇게 볼 수 있습니다.
브라우저에서 회원 이름 전송
↓
Controller가 요청 받기
↓
Service가 회원 등록 처리
↓
Repository가 회원 저장
↓
결과를 브라우저에 응답
회원 조회 흐름도 비슷합니다.
브라우저에서 회원 목록 요청
↓
Controller가 요청 받기
↓
Service가 회원 목록 요청
↓
Repository가 저장된 회원 목록 반환
↓
브라우저에 결과 출력
처음에는 이 흐름만 잡아도 충분합니다.
1. 회원 객체 만들기
먼저 회원 정보를 담을 객체가 필요합니다.
Member.java 파일을 만듭니다.
src/main/java/com/example/demo/Member.java
코드는 아래처럼 작성합니다.
package com.example.demo;
public class Member {
private Long id;
private String name;
public Member(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
}
Member 객체는 왜 필요할까?
회원 정보를 문자열 하나로만 다룰 수도 있습니다.
예를 들어 "kim"처럼 이름만 저장할 수도 있습니다.
하지만 실제 서비스에서는 회원에게 여러 정보가 붙습니다.
- 회원 ID
- 이름
- 이메일
- 가입일
- 권한
그래서 회원 하나를 표현하는 객체를 따로 만드는 것이 좋습니다.
이번 예제에서는 단순하게 id와 name만 사용하겠습니다.
private Long id;
private String name;
id는 회원을 구분하기 위한 값이고,
name은 회원 이름입니다.
2. 메모리 저장소 만들기
이제 회원을 저장할 공간이 필요합니다.
아직 DB를 사용하지 않을 것이기 때문에, 메모리 안에 회원 정보를 저장하겠습니다.
MemberRepository.java 파일을 만듭니다.
src/main/java/com/example/demo/MemberRepository.java
코드는 아래처럼 작성합니다.
package com.example.demo;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
@Repository
public class MemberRepository {
private final List<Member> members = new ArrayList<>();
private long sequence = 0L;
public Member save(String name) {
Member member = new Member(++sequence, name);
members.add(member);
return member;
}
public List<Member> findAll() {
return members;
}
}
Repository 코드 설명
1. 회원 목록 저장 공간
private final List<Member> members = new ArrayList<>();
members는 저장된 회원들을 담아두는 리스트입니다.
이번 예제에서는 DB 대신 이 리스트를 저장소처럼 사용합니다.
쉽게 말하면, 프로그램이 실행되는 동안만 유지되는 임시 회원 목록입니다.
2. 회원 ID 만들기
private long sequence = 0L;
sequence는 회원 ID를 만들기 위한 숫자입니다.
회원이 저장될 때마다 1씩 증가합니다.
처음 회원은 1, 다음 회원은 2처럼 번호가 붙습니다.
3. 회원 저장하기
public Member save(String name) {
Member member = new Member(++sequence, name);
members.add(member);
return member;
}
이 메서드는 이름을 받아서 회원 객체를 만들고, 리스트에 저장한 뒤 저장된 회원을 반환합니다.
예를 들어 kim을 저장하면 이런 회원이 만들어집니다.
id = 1
name = kim
4. 회원 전체 조회하기
public List<Member> findAll() {
return members;
}
저장된 회원 목록을 그대로 반환합니다.
나중에 DB를 연결하면 이 부분은 DB에서 회원 목록을 조회하는 코드로 바뀔 수 있습니다.
3. Service 만들기
이번에는 회원 등록과 조회 흐름을 처리할 Service를 만듭니다.
MemberService.java 파일을 만듭니다.
src/main/java/com/example/demo/MemberService.java
코드는 아래처럼 작성합니다.
package com.example.demo;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
public Member join(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("이름은 비어 있을 수 없습니다.");
}
return memberRepository.save(name);
}
public List<Member> findMembers() {
return memberRepository.findAll();
}
}
Service 코드 설명
1. Repository를 사용하기
private final MemberRepository memberRepository;
Service는 직접 데이터를 저장하지 않습니다.
저장은 Repository에게 맡깁니다.
Service는 저장하기 전에 이름이 비어 있는지 같은 규칙을 확인합니다.
2. 생성자로 Repository 받기
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
Spring Boot는 MemberRepository를 찾아서
MemberService에 넣어줍니다.
처음에는 이렇게 이해하면 됩니다.
Service가 Repository를 사용할 수 있게 연결해준다.
이런 방식을 생성자 주입이라고 부릅니다.
지금은 이름만 가볍게 알고 넘어가도 됩니다.
3. 회원 등록 로직
public Member join(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("이름은 비어 있을 수 없습니다.");
}
return memberRepository.save(name);
}
여기서는 이름이 비어 있는지 확인합니다.
이런 검사는 단순 저장이 아니라 서비스 규칙에 가깝습니다.
그래서 Service에 두는 것이 좋습니다.
조건에 문제가 없으면 Repository에 저장을 요청합니다.
4. 회원 목록 조회
public List<Member> findMembers() {
return memberRepository.findAll();
}
저장된 회원 목록을 Repository에서 가져옵니다.
Service는 Controller와 Repository 사이에서 흐름을 이어주는 역할을 합니다.
4. Controller 만들기
이제 브라우저에서 요청을 받을 Controller를 만듭니다.
MemberController.java 파일을 만듭니다.
src/main/java/com/example/demo/MemberController.java
코드는 아래처럼 작성합니다.
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class MemberController {
private final MemberService memberService;
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
@GetMapping("/members/new")
public Member join(@RequestParam("name") String name) {
return memberService.join(name);
}
@GetMapping("/members")
public List<Member> members() {
return memberService.findMembers();
}
}
Controller 코드 설명
1. @RestController
@RestController
public class MemberController {
}
@RestController는 요청을 받고, 반환값을 바로 응답으로 돌려주는 컨트롤러입니다.
이번 예제에서는 회원 객체나 회원 목록을 바로 브라우저에 보여줄 것이기 때문에 @RestController를 사용합니다.
2. Service 사용하기
private final MemberService memberService;
Controller는 직접 회원을 저장하지 않습니다.
회원 등록과 조회는 Service에게 맡깁니다.
Controller는 요청을 받고 Service를 호출하는 역할에 집중합니다.
3. 회원 등록 요청
@GetMapping("/members/new")
public Member join(@RequestParam("name") String name) {
return memberService.join(name);
}
이 코드는 /members/new 주소로 들어온 요청을 처리합니다.
예를 들어 브라우저에서 아래 주소로 접속합니다.
http://localhost:8080/members/new?name=kim
그러면 name 값으로 kim이 들어오고,
Controller는 Service의 join() 메서드를 호출합니다.
4. 회원 목록 조회 요청
@GetMapping("/members")
public List<Member> members() {
return memberService.findMembers();
}
이 코드는 저장된 회원 목록을 조회합니다.
브라우저에서 아래 주소로 접속하면 됩니다.
http://localhost:8080/members
그러면 지금까지 메모리에 저장된 회원 목록이 응답으로 나옵니다.
실행해보기
Spring Boot를 실행한 뒤, 브라우저에서 아래 주소로 접속합니다.
http://localhost:8080/members/new?name=kim
그러면 대략 이런 결과가 나올 수 있습니다.
{
"id": 1,
"name": "kim"
}
이번에는 다른 회원도 등록해보겠습니다.
http://localhost:8080/members/new?name=park
결과는 대략 이렇게 나올 수 있습니다.
{
"id": 2,
"name": "park"
}
이제 회원 목록을 조회합니다.
http://localhost:8080/members
결과는 대략 이렇게 나옵니다.
[
{
"id": 1,
"name": "kim"
},
{
"id": 2,
"name": "park"
}
]
이렇게 하면 회원 등록과 조회 흐름을 간단히 확인할 수 있습니다.
전체 흐름 다시 정리
회원 등록 요청은 이렇게 흘러갑니다.
/members/new?name=kim 요청
↓
MemberController가 name 값을 받음
↓
MemberService.join(name) 호출
↓
이름이 비어 있는지 확인
↓
MemberRepository.save(name) 호출
↓
Member 객체 생성 후 메모리에 저장
↓
저장된 Member를 응답으로 반환
회원 목록 조회 요청은 이렇게 흘러갑니다.
/members 요청
↓
MemberController가 요청 받음
↓
MemberService.findMembers() 호출
↓
MemberRepository.findAll() 호출
↓
저장된 회원 목록 반환
메모리 저장소의 한계
이번 예제에서는 ArrayList를 이용해 회원을 저장했습니다.
private final List<Member> members = new ArrayList<>();
이 방식은 배우기에는 쉽습니다.
하지만 중요한 한계가 있습니다.
서버를 끄면 저장된 데이터가 사라집니다.
왜냐하면 메모리에만 저장했기 때문입니다.
DB에 저장한 것이 아니므로, 프로그램이 종료되면 데이터도 함께 사라집니다.
그래서 실제 서비스에서는 MySQL, H2, PostgreSQL 같은 데이터베이스를 사용합니다.
하지만 처음 공부할 때는 메모리 저장소로 흐름을 먼저 익히는 것이 좋습니다.
왜 바로 DB를 쓰지 않을까?
초보 단계에서 바로 DB를 붙이면 한 번에 배워야 할 것이 많아집니다.
- DB 연결 설정
- 테이블 생성
- SQL
- JPA
- 트랜잭션
이런 개념이 한꺼번에 나오면 흐름을 잡기 어렵습니다.
그래서 먼저 메모리 저장소로 단순하게 연습합니다.
이번 단계에서 중요한 것은 DB가 아닙니다.
중요한 것은 아래 흐름입니다.
요청 → 처리 → 저장 → 조회 → 응답
이 흐름을 이해한 뒤 DB를 붙이면 훨씬 덜 헷갈립니다.
자주 헷갈리는 부분
1. Member는 테이블인가?
아직은 아닙니다.
이번 예제의 Member는 단순한 Java 객체입니다.
나중에 JPA를 배우면 이 객체가 DB 테이블과 연결될 수 있습니다.
지금은 회원 정보를 담는 상자 정도로 이해하면 됩니다.
2. Repository가 DB인가?
Repository 자체가 DB는 아닙니다.
Repository는 데이터를 저장하고 조회하는 역할을 담당하는 코드입니다.
이번 예제에서는 DB 대신 ArrayList에 저장했습니다.
나중에는 이 Repository가 DB와 연결될 수 있습니다.
3. 서버를 재시작하면 회원이 사라지는 이유
메모리에 저장했기 때문입니다.
메모리 저장소는 프로그램이 실행되는 동안만 유지됩니다.
서버를 끄면 members 리스트도 초기화됩니다.
4. 왜 Controller에서 바로 저장하지 않을까?
Controller에서 바로 저장해도 동작은 합니다.
하지만 Controller가 요청 처리와 저장 로직을 모두 가지게 되면 코드가 복잡해집니다.
그래서 요청은 Controller, 규칙은 Service, 저장은 Repository로 나눕니다.
오늘 배운 것
이번 글에서는 간단한 회원 등록 예제로 백엔드 흐름을 확인했습니다.
핵심은 아래와 같습니다.
Member객체는 회원 정보를 담는 역할MemberRepository는 회원을 저장하고 조회하는 역할MemberService는 회원 등록 규칙을 처리하는 역할MemberController는 브라우저 요청을 받는 역할- 메모리 저장소는 서버를 끄면 데이터가 사라짐
정리
이번 예제는 아주 단순하지만, 백엔드 흐름의 기본을 담고 있습니다.
요청 받기
→ 값 확인하기
→ 객체 만들기
→ 저장하기
→ 조회하기
→ 응답하기
이 흐름은 나중에 게시글, 댓글, 주문, 상품 같은 기능을 만들 때도 비슷하게 반복됩니다.
처음에는 코드가 여러 파일로 나뉘어 있어서 복잡해 보일 수 있습니다.
하지만 역할로 보면 단순합니다.
Controller = 요청 받기
Service = 규칙 처리하기
Repository = 데이터 다루기
Member = 데이터 모양
이 구조를 이해하면 Spring Boot 백엔드 개발의 기본 흐름을 잡는 데 큰 도움이 됩니다.
다음 글에서는 테스트 코드를 처음 보는 이유와 간단한 테스트 작성 방법을 정리해보겠습니다.