FastCampus X Yanolja TechSchool

ํŒจ์ŠคํŠธ์บ ํผ์ŠคX์•ผ๋†€์ž: ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ ๋ถ€ํŠธ ์บ ํ”„ - ๐ŸŽ Spring ๊ธฐ๋ฐ˜ ํ† ์ด ํ”„๋กœ์ ํŠธ 3

ํ”„๋กœ๊ทธ๋ž˜๋จธ ์˜ค์›” 2023. 11. 23.

๐ŸŽ ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

 

1๏ธโƒฃํ”„๋กœ์ ํŠธ๋‚ด์šฉ

์—ฌํ–‰, ์—ฌ์ •์„ ๊ธฐ๋กํ•˜๋Š” SNS ์„œ๋น„์Šค 3๋‹จ๊ณ„

2๏ธโƒฃํ”„๋กœ์ ํŠธ ์ฃผ์ œ ๋ฐ ํ•„์ˆ˜ ๊ตฌํ˜„ ๊ธฐ๋Šฅ ์ œ์•ˆ

์•ผ๋†€์ž

3๏ธโƒฃํ”„๋กœ์ ํŠธ ๋ชฉ์ 

Spring Boot, DB ์„ค๊ณ„, DB ํŠธ๋žœ์žญ์…˜, RESTful API  ์„ค๊ณ„ ๋Šฅ๋ ฅ ํ–ฅ์ƒ,  Spring Security, JWT, JUnit, OpenApi ํ™œ์šฉ

4๏ธโƒฃํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„

 2023๋…„ 11์›” 10์ผ (์›”) ~ 11์›” 16์ผ(๋ชฉ)

5๏ธโƒฃTeam Repository

https://github.com/FC-BE-ToyProject-Team8/TravelApp

 

GitHub - FC-BE-ToyProject-Team8/TravelApp: ์—ฌํ–‰ ๊ธฐ๋ก ์„œ๋น„์Šค SpringBoot REST API ์„œ๋ฒ„

์—ฌํ–‰ ๊ธฐ๋ก ์„œ๋น„์Šค SpringBoot REST API ์„œ๋ฒ„. Contribute to FC-BE-ToyProject-Team8/TravelApp development by creating an account on GitHub....

github.com

6๏ธโƒฃ๊ธฐ์ˆ ์Šคํƒ

์–ธ์–ด

Java17

๊ฐœ๋ฐœํ™˜๊ฒฝ ๋ฐ Dependency

Spring Boot 3.1.5

Gradle 8.3

MySQL 8

Spring Web

Spring Data JPA

JUint5

lombok

MapStruct

JWT

Redis

CI

GitHub Actions

API ๋ช…์„ธ

Swagger

 


โš–๏ธํ”„๋กœ์ ํŠธ ์ปจ๋ฒค์…˜

 

1๏ธโƒฃ์ฝ”๋”ฉ ์ปจ๋ฒค์…˜

๋ธ”๋ก์˜ ๋“ค์—ฌ์“ฐ๊ธฐ๋งŒ 4๊ณต๋ฐฑ์œผ๋กœ ์ปค์Šคํ…€ํ•˜์—ฌ ๊ตฌ๊ธ€ ์ž๋ฐ” ์ฝ”๋”ฉ ์ปจ๋ฒค์…˜์„ ์‚ฌ์šฉ

Git Hub Actions CI๋ฅผ ํ†ตํ•ด ์ฒดํฌ ์Šคํƒ€์ผ์ด ๋งž์ง€ ์•Š๋Š”๋‹ค๋ฉด Merge ๋ชปํ•˜๋„๋ก ๋ธ”๋ฝํ‚น

 

2๏ธโƒฃํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ปจ๋ฒค์…˜

Repository : @Query ์–ด๋…ธํ…Œ์ด์…˜ ๋“ฑ์„ ์‚ฌ์šฉํ•œ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•ด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ ํ•„์ˆ˜

Service : ๋‹จ์ˆœ Repository ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ด์ƒ์˜ ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•ด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ ํ•„์ˆ˜

Controller : ๋ชจ๋“  public ๋ฉ”์„œ๋“œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ ํ•„์ˆ˜

 

3๏ธโƒฃ์ƒํƒœ์ฝ”๋“œ ์ปจ๋ฒค์…˜

  • 200 : ๋ชจ๋“  ์„ฑ๊ณต
  • 400 : ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ž…๋ ฅ์„ ์ž˜๋ชปํ•œ ๊ฒฝ์šฐ
  • 401 : ์ธ์ฆ ์ •๋ณด๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ
  • 403 : ์ ‘๊ทผ ๊ถŒํ•œ์ด ์—†๋Š” ๊ฒฝ์šฐ
  • 500 : ์„œ๋ฒ„ ๋‚ด๋ถ€ ์—๋Ÿฌ

 

4๏ธโƒฃ์ปค๋ฐ‹ ๋ฉ”์„ธ์ง€ ์ปจ๋ฒค์…˜

์ปค๋ฐ‹ ์ œ๋ชฉ์€ prefix: ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ํ˜•ํƒœ๋กœ ์ž‘์„ฑ

 

prefix ๋Š” ๊ฐ๊ฐ ์ƒํ™ฉ์— ๋งž๋Š” prefix๋ฅผ ์‚ฌ์šฉ
(feat, fix, docs, test, refactor...)

๋ชจ๋“  ์ปค๋ฐ‹ ๋ฉ”์„ธ์ง€๋Š” ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑ

 

5๏ธโƒฃํ”„๋กœ์ ํŠธ ๊นƒ ๋ธŒ๋žœ์น˜ ์ „๋žต

GitFlow ์ „๋žต ( ํ”„๋กœ์ ํŠธ ๊ทœ๋ชจ ํŠน์ง•์ƒ release, hotfix ๋ธŒ๋žœ์น˜๋Š” ์ƒ๋žต)

 

feature/#

  • ์‹ค์ œ ์ž‘์—…์„ ํ•˜๋Š” ๋ธŒ๋žœ์น˜
  • ์ด์Šˆ ๋ฒˆํ˜ธ๊ฐ€ 1์ด๋ผ๋ฉด feature/1๋กœ ๋งŒ๋“ค๋ฉด ๋œ๋‹ค.
  • 'develop'์„ ๋ฒ ์ด์Šค ๋ธŒ๋žœ์น˜๋กœ ํ•˜์—ฌ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.
    • ( ๋ธŒ๋žœ์น˜ ์ƒ์„ฑ์€ ๋ฒ ์ด์Šค ๋ธŒ๋žœ์น˜[ ์ฒดํฌ์•„์›ƒ๋˜์–ด์žˆ๋Š” ๋ธŒ๋žœ์น˜ ]๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋งŒ๋“ค์–ด์ง„๋‹ค.)
  • ์ž‘์—…์ด ์™„๋ฃŒ๋˜๋ฉด develop์œผ๋กœ Pull Request๋ฅผ ๋‚ ๋ฆฐ๋‹ค.
  • 4๋ช…์˜ Approve๋ฅผ ๋ฐ›์•˜๋‹ค๋ฉด Mergeํ•œ๋‹ค.

develop

  • ๋‹ค์Œ ๋ฒ„์ „ ๊ฐœ๋ฐœ์„ ์œ„ํ•ด main์œผ๋กœ ๊ฐ€๊ธฐ ์ „ ๊ธฐ๋Šฅ ์ฝ”๋“œ๋“ค์„ ๋ชจ์•„๋‘๋Š” ๋ธŒ๋žœ์น˜
  • ์ž‘์„ฑํ•œ ๊ธฐ๋Šฅ์ด ์ž˜ ์ž‘๋™๋˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ , main์œผ๋กœ PR ๋ฐ Merge๋ฅผ ํ•˜๋ฉด ๋œ๋‹ค.
  • main์œผ๋กœ Pull Request๋ฅผ ๋‚ ๋ฆด๋•Œ๋Š” Approve ๋ฐ›๋Š” ๊ฒƒ์€ ์„ ํƒ์‚ฌํ•ญ์ด๋‹ค.

main

  • ์‹ค์ œ ์„œ๋น„์Šค๋ฅผ ์šด์˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋ธŒ๋žœ์น˜
  • main์— ๋ฐฐํฌ๊ฐ€ ๋˜๊ณ  ๋‚˜์„œ๋„ ๊ธฐ๋Šฅ์ด ์ž˜ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.

6๏ธโƒฃ๊ธฐํƒ€ ํ˜‘์˜ ์‚ฌํ•ญ

ํ˜‘์—… ๊ด€๋ จ

  • ๋ฐ์ผ๋ฆฌ ์Šคํฌ๋Ÿผ: ๋งค์ผ ์˜ค์ „ 10:00 Slack์— ์ง„ํ–‰์ƒํ™ฉ ๊ณต์œ 

 

 

๐Ÿ“‘๊ธฐํš ๋ฐ ๋‹ด๋‹น ํŒŒํŠธ

ERD

 

 

 

API ๋ช…์„ธ

 

 

๐Ÿ™‹‍โ™‚๏ธ๋‹ด๋‹น ํŒŒํŠธ

์ข‹์•„์š” ๋“ฑ๋ก,  ์ข‹์•„์š” ์ทจ์†Œ ,  ์‚ฌ์šฉ์ž ๋‹‰๋„ค์ž„์œผ๋กœ ์—ฌํ–‰ ๊ฒ€์ƒ‰

 

 

LikeService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import kr.co.fastcampus.travel.common.exception.DuplicatedLikeException;
import kr.co.fastcampus.travel.common.exception.InvalidLikeCancelException;
import kr.co.fastcampus.travel.domain.like.entity.Like;
import kr.co.fastcampus.travel.domain.like.repository.LikeRepository;
import kr.co.fastcampus.travel.domain.member.entity.Member;
import kr.co.fastcampus.travel.domain.member.service.MemberService;
import kr.co.fastcampus.travel.domain.trip.entity.Trip;
import kr.co.fastcampus.travel.domain.trip.service.TripService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional(readOnly = true)
public class LikeService {
 
    private final TripService tripService;
    private final LikeRepository likeRepository;
    private final MemberService memberService;
 
    @Transactional
    public void saveLike(Long tripId, String memberEmail) {
        Trip trip = findTrip(tripId);
        Member member = findMember(memberEmail);
        if (isExisted(trip, member)) {
            throw new DuplicatedLikeException();
        }
        likeRepository.save(createLike(trip, member));
    }
 
    @Transactional
    public void deleteLike(Long tripId, String memberEmail) {
        Trip trip = findTrip(tripId);
        Member member = findMember(memberEmail);
        if (isExisted(trip, member)) {
            likeRepository.deleteByTripAndMember(trip, member);
        } else {
            throw new InvalidLikeCancelException();
        }
    }
 
    private Trip findTrip(Long tripId) {
        return tripService.findById(tripId);
    }
 
    private Like createLike(Trip trip, Member member) {
        return Like.builder()
            .trip(trip)
            .member(member)
            .build();
    }
 
    private Member findMember(String memberEmail) {
        return memberService.findMemberByEmail(memberEmail);
    }
 
    private boolean isExisted(Trip trip, Member member) {
        return likeRepository.existsByTripAndMember(trip, member);
    }
}
 
cs

 

 

 

TripService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package kr.co.fastcampus.travel.domain.trip.service;
 
import java.util.List;
import java.util.stream.Collectors;
import kr.co.fastcampus.travel.common.exception.EntityNotFoundException;
import kr.co.fastcampus.travel.common.exception.MemberMismatchException;
import kr.co.fastcampus.travel.domain.member.entity.Member;
import kr.co.fastcampus.travel.domain.member.service.MemberService;
import kr.co.fastcampus.travel.domain.trip.entity.Trip;
import kr.co.fastcampus.travel.domain.trip.repository.TripRepository;
import kr.co.fastcampus.travel.domain.trip.service.dto.request.TripSaveDto;
import kr.co.fastcampus.travel.domain.trip.service.dto.request.TripUpdateDto;
import kr.co.fastcampus.travel.domain.trip.service.dto.response.TripDetailDto;
import kr.co.fastcampus.travel.domain.trip.service.dto.response.TripInfoDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class TripService {
 
    private final TripRepository tripRepository;
    private final MemberService memberService;
 
    public List<TripInfoDto> findAllTrips() {
        List<Trip> trips = tripRepository.findAll();
        return trips.stream()
            .map(TripInfoDto::from)
            .collect(Collectors.toList());
    }
 
    @Transactional
    public TripInfoDto addTrip(TripSaveDto dto, String memberEmail) {
        Member member = findMember(memberEmail);
        var trip = tripRepository.save(dto.toEntity(member));
        return TripInfoDto.from(trip);
    }
 
    private Member findMember(String memberEmail) {
        return memberService.findMemberByEmail(memberEmail);
    }
 
    public TripDetailDto findTripDetailById(Long id) {
        var trip = tripRepository.findFetchDetailById(id)
            .orElseThrow(EntityNotFoundException::new);
        return TripDetailDto.from(trip);
    }
 
    @Transactional
    public TripInfoDto editTrip(Long tripId, TripUpdateDto dto, String memberEmail) {
        var trip = findById(tripId);
 
        boolean isWriter = memberEmail.equals(trip.getMember().getEmail());
        if (!isWriter) {
            throw new MemberMismatchException();
        }
 
        Trip updateTrip = dto.toEntity();
        trip.update(updateTrip);
        return TripInfoDto.from(trip);
    }
 
    @Transactional
    public void deleteTrip(Long id) {
        var trip = findById(id);
        tripRepository.delete(trip);
    }
 
    public Page<TripInfoDto> findTripsByNickname(String nickname, Pageable pageable) {
        Member member = findMemberByNickname(nickname);
        Page<Trip> trips = tripRepository.findTripByMember(member, pageable);
        return trips.map(TripInfoDto::from);
    }
 
    private Member findMemberByNickname(String nickname) {
        return memberService.findByNickname(nickname);
    }
 
    public Page<TripInfoDto> searchByTripName(String tripName, Pageable pageable) {
        Page<Trip> trips = tripRepository.findAllByNameContainingIgnoreCase(tripName, pageable);
        return trips.map(TripInfoDto::from);
    }
 
    public List<TripInfoDto> findTripsByLike(String email, Pageable pageable) {
        var member = memberService.findMemberByEmail(email);
        var trips = tripRepository.findByLike(member, pageable);
        return trips.stream()
                .map(TripInfoDto::from)
                .collect(Collectors.toList());
    }
 
    public Trip findById(Long id) {
        return tripRepository.findById(id)
            .orElseThrow(EntityNotFoundException::new);
    }
}
 
cs

 

 

์‹คํ–‰ ๊ฒฐ๊ณผ  

์ข‹์•„์š” ๋“ฑ๋ก

์ข‹์•„์š” ์ทจ์†Œ

 

์‚ฌ์šฉ์ž ๋‹‰๋„ค์ž„์œผ๋กœ ์—ฌํ–‰ ๊ฒ€์ƒ‰

 

 

๐Ÿค”๊ฐœ์ธ ํšŒ๊ณ 

๋จผ์ € 3Layered architecture ์— ์ต์ˆ™ํ•ด์ ธ ๋น ๋ฅธ ์‹œ๊ฐ„๋‚ด์— ๊ฐœ๋ฐœ์„ ํ•œ ๋‚ด ์ž์‹ ์ด ๋ฟŒ๋“ฏํ–ˆ๋‹ค. ์–ด๋–ค ์ผ€์ด์Šค์— ์–ด๋–ค ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผํ• ์ง€๋„ ๋ฐ”๋กœ๋ฐ”๋กœ ๋– ์˜ค๋ฅด๊ณ , ๋กœ์ง์„ ์–ด๋–ป๊ฒŒ ๊ตฌ์ƒํ•ด์•ผํ•˜๋Š”์ง€๋„ ์Šค์Šค๋กœ์—๊ฒŒ ํ™•์‹คํ•œ ๋ฏฟ์Œ์ด ์ƒ๊ฒผ๋‹ค. ์ด๋ฒˆ ํŒ€ ํ† ์ด ํ”„๋กœ์ ํŠธ๋„ ๊ฐœ๋ฐœ์€ ๋น ๋ฅธ ์‹œ์ผ๋‚ด์— ๋๋ƒˆ๋‹ค. 

๋‹ค๋งŒ ์•„์‰ฌ์šด ์ ์ด ์กฐ๊ธˆ ์žˆ์—ˆ๋‹ค. ์ข‹์•„์š” ๋ ˆํผ๋Ÿฐ์Šค๋ฅผ ์ธ์Šคํƒ€๊ทธ๋žจ์˜ ํ•˜ํŠธ๋กœ ์ƒ๊ฐํ–ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ˜ธ๋‚ ๋‘๋‚˜ BTS ๋ฉค๋ฒ„, ์œ ๋ช… ์ธํ”Œ๋ฃจ์–ธ์„œ ๋“ฑ๊ณผ ๊ฐ™์ด ์ธ๊ธฐ ๋งŽ์€ ์‚ฌ๋žŒ์˜ ํ”ผ๋“œ์˜ ์ข‹์•„์š”๋Š” ๋งค์šฐ ๋น ๋ฅด๊ฒŒ ์˜ฌ๋ผ๊ฐ€๊ธฐ์— ์ข‹์•„์š” ์ˆ˜์— ๋Œ€ํ•œ ๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ํ•ด๋ณด๋ ค ํ–ˆ์ง€๋งŒ ์‹คํŒจํ–ˆ๋‹ค.

ํŠธ๋žœ์žญ์…˜ ๊ฒฉ๋ฆฌ์ˆ˜์ค€ ์„ค์ •( @Transactional(isolation = Isolation.READ_COMMITTED) )๊ณผ ๋‚™๊ด€์ ์ธ ๋ฝ( @Lock(LockModeType.OPTIMISTIC) )์„ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ ์‹คํŒจํ–ˆ๋‹ค. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ ์Šค๋ ˆ๋“œ ์ˆ˜๋ฅผ 10000์œผ๋กœ ์ฃผ๊ณ  ๋Œ๋ ธ์„ ๋•Œ ์„ฑ๊ณตํ•ด์„œ ํ•ด๊ฒฐํ•œ ์ค„ ์•Œ์•˜์ง€๋งŒ, ๋ช‡ ๋ฒˆ ๋” ์‹คํ–‰ํ•˜๋ฉด ์‹คํŒจ ์ผ€์ด์Šค๊ฐ€ ์ƒ๊ฒผ๋‹ค.

@Test
    @DisplayName("์ข‹์•„์š” ๋“ฑ๋ก")
    void saveLike() throws InterruptedException {
        int threadCount = 10000;
        ExecutorService executorService = Executors.newFixedThreadPool(32);
        CountDownLatch latch = new CountDownLatch(threadCount);

        //given
        Long tripId = 1L;
        Trip trip = createTrip();
        given(tripService.findById(tripId)).willReturn(trip);

        //when
        for (int i = 0; i < threadCount; i++) {
            executorService.submit(() -> {
                try {
                    Member member = createMember();
                    likeService.saveLike(tripId, member.getEmail());
                } finally {
                    latch.countDown();
                }
            });
        }
        latch.await();

        //then
        assertThat(trip.getLikeCount()).isEqualTo(threadCount);
    }

 

์—”ํ‹ฐํ‹ฐ์— ๋ฒ„์ „ (@Version)๋„ ๋‹ค ๋„ฃ๊ณ  ํ•ด๋ดค์ง€๋งŒ ๋๋๋‚ด ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•˜์—ฌ ๋„ˆ๋ฌด ์•„์‰ฌ์› ๋‹ค.

๋‹ค์Œ ๋ฏธ๋‹ˆ ํ”„๋กœ์ ํŠธ๋•Œ ์ˆ™๋ฐ• ์˜ˆ์•ฝํŒŒํŠธ์— ๋˜ ๋™์‹œ์„ฑ ์ œ์–ดํ•  ๋งŒํ•œ ์š”์†Œ๊ฐ€ ์ƒ๊ธฐ๋Š”๋ฐ, ๋‚ด๊ฐ€ ๋‚˜์„œ์„œ ํ•ด๋ณด๊ฒ ๋‹ค๊ณ  ํ•  ์˜ˆ์ •์ด๋‹ค. ์ด๋• ํ™•์‹คํ•˜๊ฒŒ ๋์œผ๋ฉด ์ข‹๊ฒ ๋‹ค.

 

@PageableDefault(size = 5) Pageable pageable ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ Pageable ์— ๋Œ€ํ•ด์„œ ์ฒ˜์Œ ์•Œ๊ฒŒ ๋ผ์„œ ์ข‹์•˜๋‹ค. ๊ธฐ์กด์—” ํŽ˜์ด์ง•๋„ค์ด์…˜๋งŒ ์•Œ๊ณ  ์žˆ์—ˆ๋Š”๋ฐ, ์ด๋Ÿฐ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์ด ์žˆ๋Š”์ง€ ๋ชฐ๋ž๋‹ค. Pageable ๋•๋ถ„์— ์•Œ์•„์„œ DB์—์„œ ํŽ˜์ด์ง•๋„ค์ด์…˜ ๋œ ์ฑ„ ๋‚˜์˜ค๋Š” ๊ฒŒ ์ •๋ง ์ข‹์•˜๋‹ค.

 

๋ฒŒ์จ 1๋‹จ๊ณ„, 2๋‹จ๊ณ„, 3๋‹จ๊ณ„ ํ•ด์˜ค๋ฉด์„œ ์Šค์Šค๋กœ ๋งŽ์ด ์„ฑ์žฅํ–ˆ๋‹ค๋Š” ๊ฒŒ ๋А๊ปด์กŒ๋‹ค. ํŠนํžˆ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ์—์„œ ์‹ค๋ ฅ์ด ๋งŽ์ด ๋Š˜์—ˆ์Œ์„ ๋А๊ผˆ๋‹ค. ๋˜ํ•œ ํด๋ฆฐ์ฝ”๋“œ ๋ฐฉ๋ฒ•๋ก ์„ ์ตœ๋Œ€ํ•œ ์‚ด๋ฆฌ๊ธฐ ์œ„ํ•ด ๋ฉ”์†Œ๋“œ๋กœ ๋”ฐ๋กœ ๋นผ๊ณ , ๋ณ€์ˆ˜ ๋ช…, ํด๋ž˜์Šค ๋ช…, ๋ฉ”์†Œ๋“œ ๋ช… ํ•˜๋‚˜ํ•˜๋‚˜ ์‹ ๊ฒฝ์“ฐ๋ฉด์„œ ํ•˜๋‹ˆ, ์‹ค๋ฌด์—์„œ๋„ ์ž˜ ํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ๋ž€ ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.(์–ด์งธ์„œ 1๋‹จ๊ณ„ ์ž๋ฐ” ์ฝ˜์†” ํ”„๋กœ์ ํŠธ๊ฐ€ ์ œ์ผ ์–ด๋ ค์› ๋˜ ๊ฑฐ ๊ฐ™์ง€ ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹)

์ข‹์€ ํŒ€์›๋“ค๊ณผ ํ•จ๊ป˜ํ•˜์—ฌ ์‹ค๋ ฅ์ด ์ผ์ทจ์›”์žฅ ํ–ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค. ํ•จ๊ป˜ ํ–ˆ๋˜ 4๋ช…์˜ ํŒ€์›๋“ค์—๊ฒŒ ๊ฐ์‚ฌํ•จ์„ ์ „ํ•œ๋‹ค. 

 

๋Œ“๊ธ€