๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

Spring Boot์—์„œ Google Cloud Storage(GCS) ๋‹ค๋ฃจ๊ธฐ

์†Œ๊ฐœ ๐ŸŽฏ

์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ Google Cloud Storage(GCS)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ์„ ์—…๋กœ๋“œ, ๋‹ค์šด๋กœ๋“œ, ์‚ญ์ œํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. GCS๋Š” Google Cloud Platform์—์„œ ์ œ๊ณตํ•˜๋Š” ํ™•์žฅ์„ฑ ์žˆ๋Š” ๊ฐ์ฒด ์Šคํ† ๋ฆฌ์ง€ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.

 

 

๋จผ์ € ์•„๋ž˜์˜ ๊ตฌ๊ธ€ ํด๋ผ์šฐ๋“œ ์ฝ˜์†”์— ๋“ค์–ด๊ฐ€์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด

 

https://cloud.google.com/cloud-console

 

https://cloud.google.com/cloud-console

 

cloud.google.com

 

 

์•„๋ž˜์˜ ์‚ฌ์ง„ ์ฒ˜๋Ÿผ 90์ผ๋™์•ˆ ๋ฌด๋ฃŒ ํฌ๋ ˆ๋”ง์„ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์ €ํฌ๋Š” ์ด ํฌ๋ ˆ๋”ง์„ ์‚ฌ์šฉํ•  ๊ฒ๋‹ˆ๋‹ค.

์•„ ๊ทธ๋ฆฌ๊ณ  ์ผ๋ฐ˜ ๊ณ„์ • ํ™œ์„ฑํ™”๋Š” ๋ฌด๋ฃŒ ํฌ๋ ˆ๋”ง์„ ์ „๋ถ€ ์‚ฌ์šฉํ•˜๋ฉด ์ดํ›„์— ์‚ฌ์šฉํ•œ ๋งŒํผ ๋น„์šฉ์„ ์ง€๋ถˆํ•˜๋Š” ๋ฐฉ์‹์ด๋ผ ๋”ฐ๋กœ ์„ค์ •์€ ์•„ํ•˜์…”๋„ ๋ฉ๋‹ˆ๋‹ค.(90์ผ ๋˜๋Š” ํฌ๋ ˆ๋”ง ์ „๋ถ€ ๋‹ค ์‚ฌ์šฉ ํ›„ ์ด์–ด์„œ ๊ณ„์† ์‚ฌ์šฉํ•˜์‹œ๊ณ  ์‹ถ์€ ๋ถ„๋“ค๋งŒ)

๊ทธ๋ฆฌ๊ณ  ์ขŒ์ธก ์ƒ๋‹จ์˜ ๋ฉ”๋‰ด๋ฐ”๋ฅผ ํด๋ฆญํ•ด์„œ ํด๋ผ์šฐ๋“œ ์Šคํ† ๋ฆฌ์ง€๋ฅผ ํด๋ฆญ ํ›„ ๋ฒ„ํ‚ท์œผ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

๋ฒ„ํ‚ท์„ ๋ˆŒ๋Ÿฌ์„œ ๋“ค์–ด์˜ค์‹œ๋ฉด ์•„๋ž˜์˜ ํ™”๋ฉด์ด ๋œจ๋Š”๋ฐ ๋ฒ„ํ‚ท ๋งŒ๋“ค๊ธฐ๋ฅผ ์„ ํƒํ•ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์•„๋ž˜ ํ™”๋ฉด์—์„œ ์ด๋ฆ„ ์ •ํ•ด์ฃผ์‹œ๊ณ , ๋ฐ์ดํ„ฐ ์ €์žฅ ์œ„์น˜๋Š” Region ์„œ์šธ ์„ ํƒํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ์Šคํ† ๋ฆฌ์ง€ ํด๋ž˜์Šค๋Š” Standard, ๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์˜ ํ™”๋ฉด์˜ ํŒŒ๋ž€์ƒ‰ ํ‘œ์‹œ๋œ ๊ณณ์˜ ์ฒดํฌ๋ฅผ ํ•ด์ œํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ดํ›„์—” ๊ณ„์†์„ ๋ˆŒ๋Ÿฌ์„œ ๋ฒ„ํ‚ท์„ ๋งŒ๋“ค์–ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์•„๋ž˜์™€ ๊ฐ™์ด ์ƒ์„ฑ๋œ ๊ฒƒ์ด ํ™•์ธ์ด ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋‹ค์‹œ ์ขŒ์ธก ์ƒ๋‹จ์˜ ๋ฉ”๋‰ด์—์„œ  IAM ๋ฐ ๊ด€๋ฆฌ์ž์˜ ์„œ๋น„์Šค ๊ณ„์ •์„ ์„ ํƒ.

 

์„œ๋น„์Šค ๊ณ„์ •์— ๋“ค์–ด์˜ค๋ฉด ํ˜„์žฌ ํ™”๋ฉด์ด ๋œจ๋Š”๋ฐ, ์•„๋ž˜๋Š” ์˜ˆ์ „์— ์ƒ์„ฑํ•ด๋‘” ์„œ๋น„์Šค ๊ณ„์ •์ด๊ณ , ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์ค„ ๊ฒƒ์ด๋‹ค. ํŒŒ๋ž€์ƒ‰์œผ๋กœ ํ‘œ์‹œ๋œ ์„œ๋น„์Šค ๊ณ„์ • ์ถ”๊ฐ€ ํด๋ฆญ.

์•„๋ž˜์˜ ์„ธ ๊ฐ€์ง€ ์—ญํ• ์„ ์ถ”๊ฐ€.

 

์ด์ œ ์™„๋ฃŒํ•ด์„œ ์ƒ์„ฑํ•˜๋ฉด ์•„๋ž˜ ์ƒˆ๋กœ์šด ์„œ๋น„์Šค ๊ณ„์ •์ด ์ƒ์„ฑ๋œ ๊ฒƒ์ด ๋ณด์ผ ๊ฒƒ์ด๋‹ค. ํด๋ฆญํ•˜์—ฌ ๋“ค์–ด๊ฐ€์ค๋‹ˆ๋‹ค.

ํด๋ฆญํ•˜์—ฌ ์„œ๋น„์Šค ๊ณ„์ •์— ๋“ค์–ด์™€์„œ ํ‚ค๋ฅผ ์„ ํƒ ํ›„ ์ƒˆ ํ‚ค ๋งŒ๋“ค๊ธฐ๋ฅผ ์„ ํƒ. JSON์œผ๋กœ ๋งŒ๋“ค๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋ฉด ์ด์ œ ํ‚ค๊ฐ€ ์ƒ์„ฑ์ด ๋˜๊ณ  ์ž๋™์œผ๋กœ ๋‹ค์šด๋กœ๋“œ๊ฐ€ ๋œ๋‹ค.

์ด ํŒŒ์ผ์€ ๋‚˜์ค‘์— resources ๋””๋ ‰ํ† ๋ฆฌ์— ๋ณด๊ด€ํ•ด์•ผ ํ•˜๋‹ˆ ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ๊ธฐ์–ตํ•ด๋‘๋ก ํ•ฉ์‹œ๋‹ค.

 

ํ™˜๊ฒฝ ์„ค์ • โš™๏ธ

build.gradle ์˜์กด์„ฑ ์ถ”๊ฐ€

implementation group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter', version: '1.2.5.RELEASE'
implementation group: 'org.springframework.cloud', name: 'spring-cloud-gcp-storage', version: '1.2.5.RELEASE'

 

๋ฒ„ํ‚ท ์ด๋ฆ„์€ ์‹œ์ž‘ํ•  ๋•Œ ์ƒ์„ฑํ–ˆ๋˜ ์ด๋ฆ„, ํ”„๋กœ์ ํŠธ ID๋Š” ํด๋ผ์šฐ๋“œ ์ฝ˜์†”์—์„œ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์€ ์ œ์ด์Šจ ํ‚ค ํŒŒ์ผ ์•ˆ์—์„œ ํ™•์ธ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

application.yml ์„ค์ •

spring
   cloud:
     gcp:
       storage:
         credentials:
             location: classpath:[ํ‚ค ์ด๋ฆ„].json
         project-id: [ํ”„๋กœ์ ํŠธ ID]
         bucket: [๋ฒ„ํ‚ท ์ด๋ฆ„]

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ๐Ÿ“

src/main/java/com/office/
โ”œโ”€โ”€ controller
โ”‚   โ””โ”€โ”€ GCSController.java
โ”œโ”€โ”€ service
โ”‚   โ””โ”€โ”€ GCSService.java
โ”œโ”€โ”€ dto
โ”‚   โ”œโ”€โ”€ GCSRequest.java
โ”‚   โ””โ”€โ”€ GCSResponse.java
โ””โ”€โ”€ exception
    โ””โ”€โ”€ GCSException.java
โ””โ”€โ”€ resources
    โ””โ”€โ”€ [ํ‚ค ํŒŒ์ผ ๋ถ™์—ฌ๋„ฃ๊ธฐ]

๊ตฌํ˜„ํ•˜๊ธฐ ๐Ÿ’ป

์˜ˆ์™ธ ์ฒ˜๋ฆฌ

๋จผ์ € GCS ๊ด€๋ จ ์ž‘์—… ์ค‘ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์ปค์Šคํ…€ ์˜ˆ์™ธ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

public class GCSException extends RuntimeException {
    public GCSException(String message) {
        super(message);
    }

    public GCSException(String message, Throwable cause) {
        super(message, cause);
    }
}

Controller ๊ตฌํ˜„ ๐ŸŽฎ

REST API ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ œ๊ณตํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/gcs")
public class GCSController {
    private final GCSService gcsService;

    @PostMapping("/upload")
    public ResponseEntity<GCSResponse> uploadObject(@ModelAttribute GCSRequest gcsRequest) throws GCSException {
        return ResponseEntity.ok(gcsService.uploadObject(gcsRequest));
    }

    @GetMapping("/download/{fileName}")
    public ResponseEntity<GCSResponse> downloadObject(@PathVariable String fileName) throws GCSException {
        return ResponseEntity.ok(gcsService.downloadObject(fileName));
    }

    @DeleteMapping("/{fileName}")
    public ResponseEntity<Void> deleteObject(@PathVariable String fileName) throws GCSException {
        gcsService.deleteObject(fileName);
        return ResponseEntity.ok().build();
    }

    @GetMapping("/list")
    public ResponseEntity<List<GCSResponse>> listObjects() throws GCSException {
        return ResponseEntity.ok(gcsService.listObjects());
    }

    @ExceptionHandler(GCSException.class)
    public ResponseEntity<String> handleGCSException(GCSException e) {
        log.error("GCS operation failed", e);
        return ResponseEntity.badRequest().body(e.getMessage());
    }
}

Service ๊ตฌํ˜„ โšก

GCS์™€์˜ ์‹ค์ œ ์ƒํ˜ธ์ž‘์šฉ์„ ๋‹ด๋‹นํ•˜๋Š” ์„œ๋น„์Šค ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.

@Slf4j
@Service
public class GCSService {
    @Value("${spring.cloud.gcp.storage.bucket}")
    private String bucketName;

    @Value("${spring.cloud.gcp.storage.credentials.location}")
    private String keyFileName;

    private Storage storage;

    @PostConstruct
    public void init() throws GCSException {
        try {
            InputStream keyFile = ResourceUtils.getURL(keyFileName).openStream();
            storage = StorageOptions.newBuilder()
                    .setCredentials(GoogleCredentials.fromStream(keyFile))
                    .build()
                    .getService();
            log.info("GCS Service initialized successfully");
        } catch (IOException e) {
            log.error("Failed to initialize GCS Service", e);
            throw new GCSException("Failed to initialize GCS service", e);
        }
    }

    // ํŒŒ์ผ ์—…๋กœ๋“œ ๋ฉ”์„œ๋“œ
    public GCSResponse uploadObject(GCSRequest request) throws GCSException {
        validateUploadRequest(request);
        try {
            MultipartFile file = request.getFile();
            String fileName = generateUniqueFileName(file.getOriginalFilename());
            BlobInfo blobInfo = BlobInfo.newBuilder(bucketName, fileName)
                    .setContentType(file.getContentType())
                    .build();
            Blob blob = storage.create(blobInfo, file.getInputStream());
            return createGCSResponse(blob);
        } catch (IOException e) {
            throw new GCSException("Failed to upload file", e);
        }
    }

    // ๊ธฐํƒ€ ๋ฉ”์„œ๋“œ ๊ตฌํ˜„...
}

DTO ๊ตฌํ˜„ ๐Ÿ“ฆ

๋ฐ์ดํ„ฐ ์ „์†ก์„ ์œ„ํ•œ DTO ํด๋ž˜์Šค๋“ค์ž…๋‹ˆ๋‹ค.

@Data
public class GCSRequest {
    private String name;
    private MultipartFile file;
}

@Data
@Builder
public class GCSResponse {
    private String fileName;
    private String downloadUrl;
    private Long fileSize;
    private String contentType;
    private String uploadTime;
}

์ฃผ์š” ๊ธฐ๋Šฅ ๐ŸŽฏ

  1. ํŒŒ์ผ ์—…๋กœ๋“œ ๐Ÿ“ค
    • ์œ ๋‹ˆํฌํ•œ ํŒŒ์ผ๋ช… ์ƒ์„ฑ
    • ํŒŒ์ผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์„ค์ •
    • ์—…๋กœ๋“œ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜
  2. ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๐Ÿ“ฅ
    • ํŒŒ์ผ๋ช…์œผ๋กœ ๊ฐ์ฒด ์กฐํšŒ
    • ๋‹ค์šด๋กœ๋“œ URL ์ œ๊ณต
  3. ํŒŒ์ผ ์‚ญ์ œ ๐Ÿ—‘๏ธ
    • ์ง€์ •๋œ ํŒŒ์ผ ์‚ญ์ œ
    • ์‚ญ์ œ ๊ฒฐ๊ณผ ํ™•์ธ
  4. ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ ๐Ÿ“‹
    • ๋ฒ„ํ‚ท ๋‚ด ๋ชจ๋“  ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ
    • ํŒŒ์ผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํฌํ•จ

API ํ…Œ์ŠคํŠธํ•˜๊ธฐ ๐Ÿงช

Postman์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„๋œ API๋ฅผ ํ…Œ์ŠคํŠธํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

1. ํŒŒ์ผ ์—…๋กœ๋“œ ํ…Œ์ŠคํŠธ ๐Ÿ“ค

Request:

  • Method: POST
  • URL: http://localhost:8080/api/gcs/upload
  • Headers:
    • Content-Type: multipart/form-data
  • Body (form-data):
    • Key: file (Type: File)
    • Key: name (Type: Text)

 

POST /api/gcs/upload HTTP/1.1
Host: localhost:8080
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="test.pdf"
Content-Type: application/pdf

[ํŒŒ์ผ ๋‚ด์šฉ]
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="name"

test-document
------WebKitFormBoundary7MA4YWxkTrZu0gW--

 

Response Example:

{
    "fileName": "6c1a4d8e-7f3c-4b2a-9c0f-7d9b9e123456.pdf",
    "downloadUrl": "https://storage.googleapis.com/[bucket-name]/6c1a4d8e-7f3c-4b2a-9c0f-7d9b9e123456.pdf",
    "fileSize": 15234,
    "contentType": "application/pdf",
    "uploadTime": "2024-01-20T09:30:15.123Z"
}

2. ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ ํ…Œ์ŠคํŠธ ๐Ÿ“‹

Request:

  • Method: GET
  • URL: http://localhost:8080/api/gcs/list
GET /api/gcs/list HTTP/1.1
Host: localhost:8080

Response Example:

[
    {
        "fileName": "6c1a4d8e-7f3c-4b2a-9c0f-7d9b9e123456.pdf",
        "downloadUrl": "https://storage.googleapis.com/[bucket-name]/6c1a4d8e-7f3c-4b2a-9c0f-7d9b9e123456.pdf",
        "fileSize": 15234,
        "contentType": "application/pdf",
        "uploadTime": "2024-01-20T09:30:15.123Z"
    },
    {
        "fileName": "7d2b5e9f-8g4d-5c3b-0d1e-8e0c0f234567.jpg",
        "downloadUrl": "https://storage.googleapis.com/[bucket-name]/7d2b5e9f-8g4d-5c3b-0d1e-8e0c0f234567.jpg",
        "fileSize": 45678,
        "contentType": "image/jpeg",
        "uploadTime": "2024-01-20T10:15:30.456Z"
    }
]

3. ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ํ…Œ์ŠคํŠธ ๐Ÿ“ฅ

Request:

  • Method: GET
  • URL: http://localhost:8080/api/gcs/download/{fileName}
GET /api/gcs/download/6c1a4d8e-7f3c-4b2a-9c0f-7d9b9e123456.pdf HTTP/1.1
Host: localhost:8080

Response Example:

{
    "fileName": "6c1a4d8e-7f3c-4b2a-9c0f-7d9b9e123456.pdf",
    "downloadUrl": "https://storage.googleapis.com/[bucket-name]/6c1a4d8e-7f3c-4b2a-9c0f-7d9b9e123456.pdf",
    "fileSize": 15234,
    "contentType": "application/pdf",
    "uploadTime": "2024-01-20T09:30:15.123Z"
}

4. ํŒŒ์ผ ์‚ญ์ œ ํ…Œ์ŠคํŠธ ๐Ÿ—‘๏ธ

Request:

  • Method: DELETE
  • URL: http://localhost:8080/api/gcs/delete/{fileName}
DELETE /api/gcs/6c1a4d8e-7f3c-4b2a-9c0f-7d9b9e123456.pdf HTTP/1.1
Host: localhost:8080

Response:

  • Status: 200 OK
  • Body: Empty

์—๋Ÿฌ ์‘๋‹ต ์˜ˆ์‹œ โš ๏ธ

ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ:

{
    "error": "File not found: non-existent-file.pdf",
    "status": 400
}

ํŒŒ์ผ ์—…๋กœ๋“œ ์‹คํŒจ ์‹œ:

{
    "error": "Failed to upload file: Invalid file format",
    "status": 400
}

Postman ํ™˜๊ฒฝ ์„ค์ • ํŒ ๐Ÿ’ก

  1. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
  2. baseUrl: http://localhost:8080 bucketName: your-bucket-name
  3. Collection ๋ณ€์ˆ˜ ์„ค์ •
    • API ๊ฒฝ๋กœ: /api/gcs
    • ๊ณตํ†ต ํ—ค๋” ์„ค์ •
  4. ํ…Œ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ ์˜ˆ์‹œ
  5. // ํŒŒ์ผ ์—…๋กœ๋“œ ํ›„ ํ…Œ์ŠคํŠธ pm.test("Upload successful", function () { phttp://m.response.to.have.status(200); phttp://m.response.to.have.jsonSchema({ required: ['fileName', 'downloadUrl', 'fileSize'] }); });

์ฃผ์˜์‚ฌํ•ญ ๐Ÿšจ

  1. ํŒŒ์ผ ์—…๋กœ๋“œ ์‹œ ์ตœ๋Œ€ ํŒŒ์ผ ํฌ๊ธฐ ์ œํ•œ์„ ํ™•์ธํ•˜์„ธ์š”.
  2. multipart/form-data ํ˜•์‹์œผ๋กœ ์ „์†กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  3. ํŒŒ์ผ๋ช…์— ํŠน์ˆ˜๋ฌธ์ž๊ฐ€ ํฌํ•จ๋œ ๊ฒฝ์šฐ URL ์ธ์ฝ”๋”ฉ์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  4. ํฐ ํŒŒ์ผ ์—…๋กœ๋“œ ์‹œ ํƒ€์ž„์•„์›ƒ ์„ค์ •์„ ์ ์ ˆํžˆ ์กฐ์ •ํ•˜์„ธ์š”.

๋งˆ์น˜๋ฉฐ โœจ

์ด๋ ‡๊ฒŒ Spring Boot์—์„œ Google Cloud Storage๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์œ„ ์ฝ”๋“œ๋Š” ๊ธฐ๋ณธ์ ์ธ ํŒŒ์ผ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ ๊ฒƒ์œผ๋กœ, ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋ณด์•ˆ, ์„ฑ๋Šฅ, ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋“ฑ์„ ๊ณ ๋ คํ•˜์—ฌ ์ถ”๊ฐ€์ ์ธ ๊ตฌํ˜„์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.