[{"content":"2023년 9월 19일, 그토록 기다리던 Java 21가 출시되었습니다. 대부분의 사람들의 관심이 Virtual Thread으로 집중되어 있는 한편 저는 매턴 매칭(Pattern Matching)에 눈길이 갔습니다. 현재 오픈소스 컨트리뷰션 아카데미에서 ZIO 팀의 멘티로 활동하며 해당 기능을 자주 활용한 경험이 있기 때문입니다.\nZIO는 Scala의 비동기 동시성 라이브러리입니다.\n그래서 이번 포스팅에서는 ZIO 스터디에서 진행하였던 예시를 통해 패턴 매칭이 무엇인지에 대해 알아보고, Java에 패턴 매칭을 구현하기 위한 프로젝트인 Project Amber에 대해 소개해드리고자 합니다. 끝으로는 Scala와 동일한 예시를 Java 21의 패턴 매칭을 활용하여 구현해보겠습니다.\nPattern Matching with Scala Pattern Matching(매턴 매칭)은 식에 대한 패턴을 체크하는 기능입니다. 이번 챕터에서는 Tour of scala의 예시와 함께 패턴 매칭이 무엇인지에 대해서 간략하게 알아보겠습니다.\n기본 문법 Scala에서는 Pattern Matching을 위해 다음과 같은 키워드를 제공합니다.\nmatch: 패턴 매칭을 시작하는 키워드 case: 분기를 정의하는 키워드 import scala.util.Random val x: Int = Random.nextInt(10) x match case 0 =\u0026gt; \u0026#34;zero\u0026#34; case 1 =\u0026gt; \u0026#34;one\u0026#34; case 2 =\u0026gt; \u0026#34;two\u0026#34; case _ =\u0026gt; \u0026#34;other\u0026#34; 위 예시는 0부터 9까지의 랜덤한 값이 들어있는 변수 x를 선언하고, x의 값에 따라 분기합니다. 자바의 switch 문과 유사하며 default 키워드 대신 언더스코어(_)를 사용한다는 차이가 있습니다.\n케이스 클래스 Scala의 패턴 매칭은 케이스 클래스(Case Classes)와 함께 사용하면 더욱 강력한 기능을 제공합니다.\nsealed trait Notification case class Email(sender: String, title: String, body: String) extends Notification case class SMS(caller: String, message: String) extends Notification case class VoiceRecording(contactName: String, link: String) extends Notification 이번 예제에서는 알림(Notification)의 형태에 따라 각기 다른 메시지를 전달하는 기능을 구현해보겠습니다. 우선 Notification이라는 trait을 정의하고 이를 상속하는 Email, SMS, VoiceRecording이라는 케이스 클래스들을 정의합니다.\ntrait은 Java의 Interface와, case class는 Java의 Recode와 유사한 개념입니다.\ndef showNotification(notification: Notification): String = { notification match { case Email(sender, title, _) =\u0026gt; s\u0026#34;You got an email from $sender with title: $title\u0026#34; case SMS(number, message) =\u0026gt; s\u0026#34;You got an SMS from $number! Message: $message\u0026#34; case VoiceRecording(name, link) =\u0026gt; s\u0026#34;you received a Voice Recording from $name! Click the link to hear it: $link\u0026#34; } } showNotification이라는 메서드를 선언합니다. 해당 메서드는 Notification을 매개변수를 받으며 구체적인 타입에 따라 다른 문자열을 생성합니다. 이 경우의 언더스코어(_)는 불필요한 값을 무시하는 문법입니다. Email의 경우 body를 사용하지 않기 때문에 언더스코어를 사용하여 무시하였습니다.\nval someSms = SMS(\u0026#34;12345\u0026#34;, \u0026#34;Are you there?\u0026#34;) val someVoiceRecording = VoiceRecording(\u0026#34;Tom\u0026#34;, \u0026#34;voicerecording.org/id/123\u0026#34;) println(showNotification(someSms)) // prints You got an SMS from 12345! Message: Are you there? println(showNotification(someVoiceRecording)) // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123 이제 앞서 선언한 메서드를 호출해 보겠습니다. SMS와 VoiceRecording 각각 매개변수로 전달하였더니, 서로 다른 메시지가 출력되는 것을 볼 수 있습니다.\nProject Amber Project Amber는 간결하지만 생산적인 자바 언어를 만들기 위한 프로젝트입니다. 널리 알려진 JEP(JDK Enhancement Proposal)에는 JEP 395: Records와 JEP 409: Sealed Classes가 있습니다. 이번 챕터에서는 Java 14에서 21까지 적용된 주요 Pattern Matching 관련 JEP들을 살펴보도록 하겠습니다.\nJEP는 JDK 개선을 위해 오라클이 초안을 작성하는 프로세스이며, 이러한 JEP들은 프로젝트라는 단위로 묶여서 관리됩니다.\nSwitch Expressions JEP 361: Switch Expressions\nSwitch 식(Switch Expressions)은 Java 14에서 도입되었으며, Switch 문(Switch Statements)의 개선 버전입니다. 간혹 국내 블로그 등지에서 Switch Expressions를 개선된 스위치문으로 소개하는 경우가 있습니다. 하지만 명확하게 이는 틀린 명칭입니다. 프로그램의 동작만 정의하는 Statements(문)과 값을 반환하는 Expressions(식)은 서로 다른 개념이기 때문입니다.\nAS-IS Switch 문은 특정 변수의 비교하여 다양한 조건을 처리하는 조건 제어문입니다. 사용의 편리함 때문에 많은 개발자들이 활용하는 구문이지만, 몇 가지 단점이 존재합니다.\nfor (NodeFlag flag : source) { switch (flag) { case NOFLAGS: flags.add(Flag.NOFLAGS); break; case EVENTUAL_FAIL: flags.add(Flag.PFAIL); break; case FAIL: flags.add(Flag.FAIL); break; case HANDSHAKE: flags.add(Flag.HANDSHAKE); break; case MASTER: flags.add(Flag.MASTER); break; case MYSELF: flags.add(Flag.MYSELF); break; case NOADDR: flags.add(Flag.NOADDR); break; case SLAVE: case REPLICA: flags.add(Flag.REPLICA); break; } } 위 예시 코드는 Switch 문이 가지고 있는 문제점이 잘 드러나 있습니다. 조건의 개수에 비해 지나치게 코드가 비대해집니다. 또한 Switch 문은 break 키워드를 사용하지 않으면 아래 브랜치의 로직까지 수행하는 fail through라는 특징이 있습니다. 이러한 특징 때문에 break를 빼먹는 실수를 하게 되면 논리적인 오류가 발생할 수 있습니다.\nTO-BE Switch 식은 앞서 설명한 Switch 문의 단점들을 모두 해결해줍니다.\nfor (NodeFlag flag : source) { switch (flag) { case NOFLAGS -\u0026gt; flags.add(Flag.NOFLAGS); case EVENTUAL_FAIL -\u0026gt; flags.add(Flag.PFAIL); case FAIL -\u0026gt; flags.add(Flag.FAIL); case HANDSHAKE -\u0026gt; flags.add(Flag.HANDSHAKE); case MASTER -\u0026gt; flags.add(Flag.MASTER); case MYSELF -\u0026gt; flags.add(Flag.MYSELF); case NOADDR -\u0026gt; flags.add(Flag.NOADDR); case SLAVE, REPLICA -\u0026gt; flags.add(Flag.REPLICA); } } Switch 식은 람다식과 유사한 -\u0026gt;를 사용하여 코드를 명시적이면서 간결하게 표현할 수 있습니다. 게다가 fail through라는 특징이 없으므로 논리적인 오류가 발생할 위험성이 줄어듭니다.\nSwitch 문 vs Switch 식 switch (returnType) { case BOOLEAN: return ScriptOutputType.BOOLEAN; case MULTI: return ScriptOutputType.MULTI; case VALUE: return ScriptOutputType.VALUE; case INTEGER: return ScriptOutputType.INTEGER; case STATUS: return ScriptOutputType.STATUS; default: throw new IllegalArgumentException(\u0026#34;Return type \u0026#34; + returnType + \u0026#34; is not a supported script output type\u0026#34;); } 위 예시 코드는 앞서 살펴본 코드와 살짝은 다른 케이스입니다. case 브랜치 내부에서 return문을 사용하여 값을 반환합니다. 그렇기 때문에 break를 사용할 필요가 없습니다. 이러한 경우는 Switch 식을 통해 어떻게 개선할 수 있을까요?\nreturn switch (returnType) { case BOOLEAN -\u0026gt; ScriptOutputType.BOOLEAN; case MULTI -\u0026gt; ScriptOutputType.MULTI; case VALUE -\u0026gt; ScriptOutputType.VALUE; case INTEGER -\u0026gt; ScriptOutputType.INTEGER; case STATUS -\u0026gt; ScriptOutputType.STATUS; default -\u0026gt; throw new IllegalArgumentException(\u0026#34;Return type \u0026#34; + returnType + \u0026#34; is not a supported script output type\u0026#34;); }; 앞서 Switch 식은 값을 반환하는 특징을 가진다고 설명해드렸습니다. 이러한 특징을 이용한다면 switch 키워드 그 자체를 반환하는 것이 가능합니다. 이를 통해 기존 코드의 반복되는 return문을 제거할 수 있습니다.\n이번 챕터에서 사용된 예시들은 Spring Data Redis의 코드를 개선한 사례에서 발췌하였습니다. 자세한 내용이 굼금하시다면 제가 올린 PR인 Spring Data Redis #2706을 참고해주세요.\nPattern Matching for instanceof JEP 394: Pattern Matching for instanceof\n이번에 살펴볼 기능은 Java 16에서 추가되었습니다. 자바의 클래스 타입을 비교하는 instanceof를 패턴 매칭으로 사용할 수 있도록 해주는 기능입니다.\nAS-IS if (obj instanceof String) { String s = (String)obj; ... use s ... } 혹시 Java 16 이전 버전을 사용하고 계신가요? 그렇다면 다음과 같이 if문으로 타입을 확인하였더라도 내부에서 형변환을 수행해주어야 합니다. 하지만 이미 타입을 확인하였는데 형변환을 해주어야 된다니 무언가 어색하다는 생각이 듭니다.\nTO-BE if (obj instanceof String s) { ... use s ... } 그래서 Java 16에서는 이러한 불필요한 형변환 작업을 하지 않더라도 타입이 매칭되도록 개선하였습니다. 그래서 이미 타입을 instanceof로 확인하였다면 바로 특정 타입의 변수로 사용할 수 있습니다.\nPattern Matching for switch JEP 441: Pattern Matching for switch\n여기서부터는 드디어 이번 Java 21에 추가된 기능을 소개해드리겠습니다. 스위치를 위한 패턴 매칭은 Java 14에서 추가된 Switch 식과 Java 16에서 추가된 Pattern Matching for instanceof를 결합한 기능입니다.\nAS-IS static String formatter(Object obj) { String formatted = \u0026#34;unknown\u0026#34;; if (obj instanceof Integer i) { formatted = String.format(\u0026#34;int %d\u0026#34;, i); } else if (obj instanceof Long l) { formatted = String.format(\u0026#34;long %d\u0026#34;, l); } else if (obj instanceof Double d) { formatted = String.format(\u0026#34;double %f\u0026#34;, d); } else if (obj instanceof String s) { formatted = String.format(\u0026#34;String %s\u0026#34;, s); } return formatted; } 앞서 살펴본 Pattern Matching for instanceof에서는 instanceof를 통해 타입을 비교하였습니다. 하지만 해당 기능은 if문과 함께 사용되어야만 했습니다.\nTO-BE static String formatterPatternSwitch(Object obj) { return switch (obj) { case Integer i -\u0026gt; String.format(\u0026#34;int %d\u0026#34;, i); case Long l -\u0026gt; String.format(\u0026#34;long %d\u0026#34;, l); case Double d -\u0026gt; String.format(\u0026#34;double %f\u0026#34;, d); case String s -\u0026gt; String.format(\u0026#34;String %s\u0026#34;, s); default -\u0026gt; obj.toString(); }; } 하지만 이제는 if문이 아닌 switch 식에서도 타입을 비교할 수 있게 되었습니다. 이를 통해 코드가 더욱 간결해지고 가독성이 좋아졌습니다. 또한 형변환 없이 바로 특정 타입의 변수로 사용할 수 있습니다.\nRecord Patterns JEP 440: Record Patterns\n레코드(Record)는 Java에서 사용되는 불변 객체 타입입니다. Record Patterns은 구조분해 할당(deconstruct)을 통해 레코드 내에 존재하는 변수들을 쉽게 사용할 수 있도록 제공합니다.\nAS-IS record Point(int x, int y) {} static void printSum(Object obj) { if (obj instanceof Point p) { int x = p.x(); int y = p.y(); System.out.println(x+y); } } 이전까지 레코드는 반드시 getter를 통해 내부의 변수에 접근할 수 있었습니다. 그래서 위 예시에서 필요한 값은 x와 y이지만 point 타입의 변수를 통해 값을 가져와야 했습니다.\nTO-BE static void printSum(Object obj) { if (obj instanceof Point(int x, int y)) { System.out.println(x+y); } } 하지만 이제는 그럴 필요가 없습니다. Record Patterns을 이용한다면 레코드 내의 필요한 값만 가져올 수 있습니다. 이를 이용한다면 간결하고 가독성이 좋은 코드를 작성할 수 있습니다.\nPattern Matching with Java 21 여기까지 Project Amber를 통해 Java에 추가된 패턴 매칭 기능들을 살펴보았습니다. 이번 챕터에서는 이 모든 기능을 함께 활용하는 방법을 소개해드리고자 합니다.\nsealed interface Notification {} record Email(String sender, String title, String body) implements Notification {} record SMS(String caller, String message) implements Notification {} record VoiceRecording(String contactName, String link) implements Notification {} 앞서 블로그 초반에 소개해드린 Pattern Matching with Scala 챕터의 예시를 살펴보겠습니다. Scala로 작성된 코드를 Java로 작성한다면 위와 같이 작성할 수 있을 것입니다. Notification을 sealed interface로 정의하고 세부적인 데이터 구조를 record로 정의합니다.\npublic static String showNotification(Notification notification) { return switch (notification) { case Email(String sender, String title, String body) -\u0026gt; String.format(\u0026#34;You got an email from %s with title: %s\u0026#34;, sender, title); case SMS(String caller, String message) -\u0026gt; String.format(\u0026#34;You got an SMS from %s! Message: %s\u0026#34;, caller, message); case VoiceRecording(String contactName, String link) -\u0026gt; String.format(\u0026#34;You received a Voice Recording from %s! Click the link to hear it: %s\u0026#34;, contactName, link); }; } 이제 showNotification 메서드도 작성해주겠습니다. Java 21에서 Pattern Matching for switch을 적용된 덕분에 Notification의 타입을 비교하고, 필요한 로직을 수행할 수 있습니다. 또한 Notification의 하위 타입들은 모두 Record이므로 Record Patterns를 적용할 수 있으므로, 구조분해 할당을 통해 필요한 멤버 변수만 가져올 수 있습니다.\nvar someSms = new SMS(\u0026#34;12345\u0026#34;, \u0026#34;Are you there?\u0026#34;); var someVoiceRecording = new VoiceRecording(\u0026#34;Tom\u0026#34;, \u0026#34;voicerecording.org/id/123\u0026#34;); System.out.println(showNotification(someSms)); // prints You got an SMS from 12345! Message: Are you there? System.out.println(showNotification(someVoiceRecording)); // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123 최종적으로 완성된 코드를 실행하면 앞서 Scala에서 실행한 것과 동일한 결과를 얻으실 수 있습니다.\n프리뷰 기능까지 적용하기 하지만 여전히 레코드 패턴 내 불필요한 인자를 무시할 수 없다는 점과 문자열 생성을 위해 String.format을 사용하는 방법은 아쉬움이 남습니다. 이러한 것을 보완한 기능들은 이번 Java 21의 프리뷰 기능으로 릴리즈 되었습니다.\n관련 JEP는 다음과 같습니다.\nJEP 430: String Templates JEP 443: Unnamed Patterns and Variables public static String showNotification(Notification notification) { return switch (notification) { case Email(String sender, String title, _) -\u0026gt; STR.\u0026#34;You got an email from \\{sender} with title: \\{title}\u0026#34;; // String interpolation case SMS(String caller, String message) -\u0026gt; STR.\u0026#34;You got an SMS from \\{caller}! Message: \\{message}\u0026#34;; case VoiceRecording(String contactName, String link) -\u0026gt; STR.\u0026#34;You received a Voice Recording from \\{contactName}! Click the link to hear it: \\{link}\u0026#34;; }; } 이러한 기능들이 모두 적용된 자바 예제는 위와 같습니다. STR 키워드는 String Templates를 사용하기 위한 키워드입니다. 이를 통해 문자열 내부에 변수를 삽입할 수 있습니다. 또한 record의 멤버 변수 중 불필요한 인자는 _를 통해 무시할 수 있습니다.\n최종적으로 프리뷰 기능까지 적용한 코드를 보면 Scala의 코드와 매우 유사하다는 사실을 깨달을 수 있습니다. 이는 Project Amber가 추구하는 방향성이 Scala와 같은 함수형의 장점을 Java에 적용하는 것이라는 사실을 보여줍니다.\n마치며 Java는 8 버전에 Lambda Expression(람다식)을 추가한 이후 함수형 프로그래밍 언어의 특징을 계속해서 적용해오고 있습니다. 그리고 이번 Java 21에서는 Pattern Matching(패턴 매칭)의 주요 기능들을 다수 적용하면서 함수형 패러다임을 적극적으로 수용하는 모습을 보여주었습니다.\n이번 릴리즈를 통해 Java가 낡은 언어가 아닌 현대적인 언어로 탈바꿈하고 있다는 것을 느낄 수 있었습니다. 모던 Java 시대에서 다른 JVM 언어들은 또 어떤 강점을 보여줄 수 있을 지 기대가 됩니다. 또한 가상 스레드(Virtual Thread)의 도입으로 인한 JVM 내 동시성 프로그래밍의 패러다임 변화는 Kotlin의 Coroutine을 대체할 수 있을 것인지도 하나의 관전 포인트입니다.\n동시에 특정 언어만 고집하는 것은 좋지 않다는 생각이 다시 한 번 들었습니다. 저는 Java가 메인 언어임에도 Scala나 Python에 관심을 가지고 공부해왔습니다. 그 덕분에 이번 Java 21의 패턴 매칭을 빠르게 이해할 수 있었으며, 이번 블로그 포스팅도 작성하는 계기가 되었습니다. 점차 언어간의 문법의 경계가 사라지는 가운데, 여러분들도 다양한 언어를 경험해보고 다양한 패러다임을 경험해보는 것은 어떨까요?\n","permalink":"https://vanslog.io/ko/posts/language/java/java-21-pattern-matching-usecase-with-scala/","summary":"고도로 발달된 Java는 Scala와 구분할 수 없다.","title":"Java 21 패턴 매칭 제대로 활용하기(with Scala)"},{"content":"통합 테스트는 Spring Data Meilisearch의 개발을 진행하는 과정에서 매우 중요한 부분입니다. 프로젝트의 핵심은 실제 Meilisearch와 통합되어 제대로 동작하는 지가 관건이기 때문입니다. 이번 포스트에서는 MeilisearchTemplate라는 실제 프로젝트 Core 모듈을 테스트하기 위해 적용한 방법에 대해서 소개해드리고자 합니다.\nSpring Data 구현체에서 Template 클래스는 데이터 저장소에 접근하기 위한 기본적인 메서드를 제공하는 클래스를 말합니다.\nTestcontainers Meilisearch 통합 테스트에서 가장 먼저 필요한 작업은 Spring Data Meilisearch가 접근할 인스턴스를 실행하는 것이었습니다. 인스턴스를 실행하는 방법에는 여러 가지가 있겠지만, 개발 환경에 구애받지 않는 테스트 환경을 구축하기 위해서 Testcontainers를 사용하였습니다.\n컨테이너 실행 Testcontainers Meilisearch 라이브러리를 사용하면 Meilisearch에 필요한 테스트 환경을 쉽게 구축할 수 있습니다. Testcontainers Meilisearch는 Testcontainers 커뮤니티 모듈로 제공되는 라이브러리로, 현재 제가 직접 개발 및 유지보수하고 있는 프로젝트입니다.\n@ExtendWith(SpringExtension.class) class MeilisearchTemplateTest { MeilisearchContainer meilisearch; @BeforeAll static void beforeAll() { meilisearch = new MeilisearchContainer().withMasterKey(\u0026#34;masterKey\u0026#34;); meilisearch.start(); } // ... } 위 예시 코드는 Meilisearch의 마스터 키를 설정하고 컨테이너를 실행합니다.\n컨테이너 연결 설정 이제 테스트 코드 상에서 Meilisearch 인스턴스와 연결해봅시다. Spring Data Meilisearch는 Meilisearch 인스턴스와 연결하는 방법으로 JavaConfig와 XML Namespace의 설정 방식을 모두 지원하지만, 이번 테스트 환경을 구축하는데 필요한 설정은 JavaConfig 방식으로 작성하였습니다.\n@Configuration class Config extends MeilisearchConfiguration { @Override public ClientConfiguration clientConfiguration() { return ClientConfiguration.builder() .connectedTo(\u0026#34;http://localhost:\u0026#34; + meilisearch.getMappedPort(7700)) .withApiKey(\u0026#34;masterKey\u0026#34;).build(); } } 위 코드에서는 connectedTo 메서드를 사용하여 Meilisearch 인스턴스의 호스트명과 포트를 지정합니다. Testcontainers를 통해 실행한 컨테이너의 외부 포트는 매번 랜덤한 값으로 바인딩됩니다. 따라서 meilisearch.getMappedPort(7700)와 같이 컨테이너의 포트를 런타임 시 동적으로 가져와야 합니다.\n전체 예시 코드 이제 전체적인 코드를 살펴보겠습니다. MeilisearchTemplate 클래스를 테스트하는 코드의 구조는 다음과 같습니다.\n컨테이너 설정과 관련된 부분을 제외하고는 생략하였습니다.\n@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = { MeilisearchTemplateTest.Config.class }) class MeilisearchTemplateTest { MeilisearchContainer meilisearch; @BeforeAll static void beforeAll() { meilisearch = new MeilisearchContainer().withMasterKey(\u0026#34;masterKey\u0026#34;); meilisearch.start(); } // ... @Configuration static class Config extends MeilisearchConfiguration { public ClientConfiguration clientConfiguration() { return ClientConfiguration.builder() .connectedTo(\u0026#34;http://localhost:\u0026#34; + meilisearch.getMappedPort(7700)) .withApiKey(\u0026#34;masterKey\u0026#34;).build(); } } } @ContextConfiguration 어노테이션을 사용하여 이너클래스인 Config를 설정으로 지정합니다. 이를 통해 Testcontainers Meilisearch를 통해 실행한 컨테이너에 연결하고 테스트를 진행할 수 있습니다.\n하지만 이와 같은 방법에도 여전히 문제가 있습니다. 테스트 파일이 많아진다면 @BeforeAll 어노테이션을 사용하여 컨테이너를 실행하는 코드를 중복해서 작성해야 하며, 중복으로 설정 파일을 생성해야 합니다.\n관점 지향 프로그래밍 여기서 생각해볼만한 주제는 관점 지향 프로그래밍(AOP)입니다.\n관점 지향 프로그래밍은 관심사의 분리를 통해 모듈성을 증가시키는 프로그래밍 기법입니다. 여기서 관심사는 핵심적인 기능과 부가적인 기능으로 나누어집니다.\n예를 들어 서로 다른 모듈들이 공통적으로 사용하는 기능이 있다면, 이는 부가적인 기능에 해당합니다. 로깅이나 보안과 같은 기능은 여러 모듈에 전역적으로 사용되기 때문에 횡단 관심사라고도 합니다.\n테스트 코드의 관심사 분리 이전 챕터에서 살펴봤던 MeilisearchTemplateTest 코드를 다시 한 번 떠올려봅시다. 해당 코드는 MeilisearchTemplate의 기능이 제대로 동작하는 지 확인하기 위한 통합 테스트 코드였습니다. 해당 코드를 AOP 관점에서 바라보면 다음과 같이 나눌 수 있을 것입니다.\n핵심 관심사: 테스트 수행 횡단 관심사: 컨테이너 실행 및 관리 만약 횡단 관심사인 컨테이너 관리를 모듈로 분리할 수 있다면 어떨까요? 테스트 코드 상에서는 테스트 수행에만 집중하게 되어 더욱 모듈성이 높은 코드를 작성할 수 있을 것입니다. 또한 여러 테스트 파일에서 컨테이너 관리에 대한 로직을 중복해서 작성할 필요도 없어질 것입니다.\nJUnit 5 확장 모델 JUnit 5를 이용하여 테스트 코드를 작성하는 경우 확장 모델(Extension Model)을 통해 횡단 관심사를 모듈로 분리해낼 수 있습니다. 확장 모델을 이용하면 기존 JUnit 5에서 제공하는 기능을 확장하여 사용할 수 있습니다.\n가장 대표적인 확장 모델은 Spring Framework에서 제공하는 SpringExtension 클래스입니다. 해당 클래스는 Spring Framework의 테스트 컨텍스트를 생성하고 관리하는 역할을 하며, 앞서 살펴본 예시 코드에서도 은연중에 사용되었습니다.\n이것과 유사한 방식으로 재사용 가능한 확장 모델을 직접 만들어 사용할 수 있습니다.\n확장 모델 구현 앞서 살펴본 예시 코드에서는 @BeforeAll 어노테이션을 사용하여 컨테이너를 실행하였습니다. 이를 확장 모델을 통해 구현하면 다음과 같습니다.\npublic class MeilisearchExtension implements BeforeAllCallback { private final Lock initLock = new ReentrantLock(); @Override public void beforeAll(ExtensionContext context) { initLock.lock(); try { new MeilisearchConnection(); } finally { initLock.unlock(); } } } MeilisearchExtension 클래스는 BeforeAllCallback 인터페이스를 구현하며, 이를 통해 @BeforeAll 어노테이션에 정의하던 기능을 여기서 구현할 수 있습니다.\nMeilisearchConnection 클래스는 컨테이너를 실행하고 연결하는 역할을 합니다. 전체 코드가 궁금하시다면 레포지토리를 참고해주세요.\nTestConfiguration 구현 이제 실행된 컨테이너의 호스트명과 포트를 가져온 설정을 제공하는 MeilisearchTestConfiguration 클래스를 구현해보겠습니다.\n@Configuration public class MeilisearchTestConfiguration extends MeilisearchConfiguration { private static final String HTTP = \u0026#34;http://\u0026#34;; private final MeilisearchConnectionInfo meilisearchConnectionInfo = MeilisearchConnection.meilisearchConnectionInfo(); @Override public ClientConfiguration clientConfiguration() { return ClientConfiguration.builder() .connectedTo(HTTP + meilisearchConnectionInfo.getHost() + \u0026#34;:\u0026#34; + meilisearchConnectionInfo.getPort()) .withApiKey(meilisearchConnectionInfo.getMasterKey()).build(); } } MeilisearchTestConfiguration 클래스는 앞서 MeilisearchExtension 클래스에서 실행한 컨테이너의 호스트명과 포트를 가져와 Meilisearch 인스턴스에 연결할 수 있는 설정을 제공합니다.\n@ExtendWith(SpringExtension.class) @ExtendWith(MeilisearchExtension.class) @ContextConfiguration(classes = { MeilisearchTestConfiguration.class }) class MeilisearchTemplateTest { // ... } 이제 더 이상 Meilisearch 컨테이너를 생성하고 연결하는 로직을 매 테스트마다 작성할 필요가 없어졌습니다. MeilisearchExtension 클래스를 통해 컨테이너를 실행하고, MeilisearchTestConfiguration 클래스를 통해 컨테이너에 연결할 수 있는 설정을 제공받아 테스트를 진행할 수 있습니다.\n테스트 어노테이션 구현 여기까지만 진행해도 충분하지만 추가적으로 @MeilisearchTest 어노테이션을 생성하여 테스트 코드를 더욱 간결하게 작성할 수 있도록 해보겠습니다.\n@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @ExtendWith(MeilisearchExtension.class) @ExtendWith(SpringExtension.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public @interface MeilisearchTest { } @MeilisearchTest 어노테이션은 MeilisearchExtension 클래스를 통해 컨테이너를 실행하고, SpringExtension 클래스를 통해 스프링 컨텍스트를 생성합니다.\n@MeilisearchTest @ContextConfiguration(classes = { MeilisearchTestConfiguration.class }) class MeilisearchTemplateTest { // ... } 이제 더 이상 @ExtendWith 어노테이션을 사용할 필요가 없어졌으며, 간단하게 @MeilisearchTest 어노테이션만을 사용하여 테스트를 진행할 수 있습니다.\n마치며 이번 포스트에서는 통합 테스트 환경을 구축하기 위해 사용한 방법들에 대해서 소개해드렸습니다. 직접 구현한 Testcontainers 라이브러리와 JUnit 5의 확장 모델로 구축한 환경인 만큼 뜻깊고 의미 있는 경험이었습니다.\n테스트 코드를 어떻게 작성하는 것이 좋은 방법인지에 대해서 많은 고민을 하였고, 이러한 시간 속에서 관점 지향 프로그래밍과도 연관지어 생각해볼 수 있었습니다.\nMeilisearch 생태계의 부족한 스프링 통합 환경을 만들어 나가자는 프로젝트 초기 목표가 점차 달성되어 가는 것이 뿌듯합니다. 앞으로도 더욱 많은 기능을 추가하여 사용자가 편리하게 사용할 수 있도록 노력하겠습니다.\n참고문헌 Testcontainers Modules - Meilisearch What is AOP? Principles of aspect-oriented programming JUnit 5 공식 문서 - 확장 모델 ","permalink":"https://vanslog.io/ko/posts/project/spring-data-meilisearch/build-integration-test-environment/","summary":"Testcontainers와 JUnit 5 확장 모델로 통합 테스트 환경 구축하기","title":"Spring Data Meilisearch #3 - 통합테스트 환경 구축"},{"content":"이번 글에서는 자바 기반의 설정을 지원하는 방법에 대해 알아보겠습니다. 스프링 3.1 버전 이후부터는 기본적으로 @Configuration 어노테이션을 사용하여 설정하는 것을 권장하고 있습니다. 그렇기 때문에 저희 라이브러리에서도 @Configuration 어노테이션을 사용하여 설정할 수 있도록 지원하고 있습니다.\n일반적인 어노테이션 기반 설정 일반적으로 @Configuration 어노테이션을 사용하여 설정하는 방법은 다음과 같습니다. Meilisearch Client 객체를 직접 생성하고 빈으로 등록한다면 다음과 같이 작성할 수 있습니다.\n@Configuration public class MeilisearchConfiguration { @Bean public Client meilisearchClient() { Config meilisearchConfig = new Config( \u0026#34;http://localhost:7700\u0026#34;, \u0026#34;masterKey\u0026#34; ); return new Client(meilisearchConfig); } } 하지만 이러한 방식은 사용자가 직접 빈을 등록해야 하는 불편함이 있습니다. 그렇기 떄문에 저희 라이브러리에서는 표준화된 방식으로 설정할 수 있도록 지원하고 있습니다.\n표준화된 설정을 제공하는 방법 우선 빈을 직접 등록하는 방식을 사용하지 않고, clientConfiguration이라는 메서드를 정의하여 설정할 수 있도록 지원하고 있습니다.\n@Configuration class CustomConfiguration extends MeilisearchConfiguration { @Override public ClientConfiguration clientConfiguration() { return ClientConfiguration.builder() .connectedToLocalhost() .withApiKey(\u0026#34;masterKey\u0026#34;) .build(); } } ClientConfiguration 객체를 빌더 패턴으로 생성하여 반환하면 됩니다. 빌더 자체가 설정에 필요한 다양한 메서드를 가지고 있기 때문에, 사용자가 원하는 설정을 쉽게 지정할 수 있습니다.\n마치며 Meilisearch 클라이언트 설정에 필요한 구체적인 클래스인 Config와 Client를 캡슐화하여 사용자가 직접 생성할 필요가 없도록 하였습니다.\n덕분에 사용자는 ClientConfiguration 인터페이스만을 활용하여 설정할 수 있게 되었고 더욱 편리하게 사용할 수 있게 되었습니다.\n","permalink":"https://vanslog.io/ko/posts/project/spring-data-meilisearch/support-configuration-with-annotation/","summary":"스프링 라이브러리에서 어노테이션 기반 설정을 지원하는 방법","title":"Spring Data Meilisearch #2 - 어노테이션 기반 설정"},{"content":"Spring Data Meilisearch 프로젝트를 진행하면서 Meilisearch 클라이언트를 스프링 빈으로 등록하는 기능을 제공해야 했습니다. 스프링 빈에 등록하는 방법은 크게 두 가지가 있습니다.\nXML 네임스페이스 기반의 설정 자바 어노테이션 기반의 설정 이번 포스팅에서는 스프링 라이브러리 단에서 XML 기반의 설정을 지원하는 방법에 대해 다뤄보겠습니다.\n만약 어노테이션 기반의 설정을 지원하는 방법에 대해 알고 싶다면 다음 글을 참고해주세요.\n네임스페이스 설정하는 방법 namespace.xml에 다음과 같이 정의하면 Meilisearch 클라이언트를 생성하고 스프링 Bean으로 등록할 수 있습니다.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xmlns:meilisearch=\u0026#34;http://www.vanslog.io/spring/data/meilisearch\u0026#34; xsi:schemaLocation=\u0026#34;http://www.vanslog.io/spring/data/meilisearch http://www.vanslog.io/spring/data/meilisearch/spring-meilisearch-1.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\u0026#34;\u0026gt; \u0026lt;meilisearch:meilisearch-client id=\u0026#34;meilisearchClient\u0026#34;/\u0026gt; \u0026lt;/beans\u0026gt; 구현 방법 XML 기반 설정을 지원하기 위해서는 다음과 같은 구현이 필요합니다.\n네임스페이스 기반 설정을 위해 XSD 정의 NamespaceHandler를 통해 네임스페이스 등록 BeanDefinitionParser를 통해 Bean 등록에 필요한 속성 파싱 FactoryBean을 통해 Meilisearch 클라이언트 생성 및 Bean 등록 XSD 정의 XSD는 XML 문서의 구조와 내용을 정의하는 스키마 언어입니다. Spring에서는 XML을 통한 설정을 지원하기 위해 XSD를 사용합니다. Spring Data Meilisearch에서 XML 기반 설정을 지원하기 위해 spring-meilisearch-1.0.xsd라는 파일을 정의하였습니다.\n해당 내용이 길어 접어두었으니 아래 버튼을 클릭하여 내용을 확인해주세요.\nspring-meilisearch-1.0.xsd \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;xsd:schema xmlns:xsd=\u0026#34;http://www.w3.org/2001/XMLSchema\u0026#34; xmlns:beans=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:tool=\u0026#34;http://www.springframework.org/schema/tool\u0026#34; xmlns=\u0026#34;http://www.vanslog.io/spring/data/meilisearch\u0026#34; targetNamespace=\u0026#34;http://www.vanslog.io/spring/data/meilisearch\u0026#34; elementFormDefault=\u0026#34;qualified\u0026#34; attributeFormDefault=\u0026#34;unqualified\u0026#34;\u0026gt; \u0026lt;xsd:import namespace=\u0026#34;http://www.springframework.org/schema/beans\u0026#34;/\u0026gt; \u0026lt;xsd:import namespace=\u0026#34;http://www.springframework.org/schema/tool\u0026#34;/\u0026gt; \u0026lt;xsd:element name=\u0026#34;meilisearch-client\u0026#34;\u0026gt; \u0026lt;xsd:annotation\u0026gt; \u0026lt;xsd:documentation/\u0026gt; \u0026lt;xsd:appinfo\u0026gt; \u0026lt;tool:assignable-to type=\u0026#34;com.meilisearch.sdk.Client\u0026#34;/\u0026gt; \u0026lt;/xsd:appinfo\u0026gt; \u0026lt;/xsd:annotation\u0026gt; \u0026lt;xsd:complexType\u0026gt; \u0026lt;xsd:complexContent\u0026gt; \u0026lt;xsd:extension base=\u0026#34;beans:identifiedType\u0026#34;\u0026gt; \u0026lt;xsd:attribute name=\u0026#34;host-url\u0026#34; type=\u0026#34;xsd:string\u0026#34; default=\u0026#34;http://localhost:7700\u0026#34;\u0026gt; \u0026lt;xsd:annotation\u0026gt; \u0026lt;xsd:documentation\u0026gt; \u0026lt;![CDATA[The host address of the Meilisearch server. The default is http://localhost:7700.]]\u0026gt; \u0026lt;/xsd:documentation\u0026gt; \u0026lt;/xsd:annotation\u0026gt; \u0026lt;/xsd:attribute\u0026gt; \u0026lt;xsd:attribute name=\u0026#34;api-key\u0026#34; type=\u0026#34;xsd:string\u0026#34;\u0026gt; \u0026lt;xsd:annotation\u0026gt; \u0026lt;xsd:documentation\u0026gt; \u0026lt;![CDATA[The API key of the Meilisearch server.]]\u0026gt; \u0026lt;/xsd:documentation\u0026gt; \u0026lt;/xsd:annotation\u0026gt; \u0026lt;/xsd:attribute\u0026gt; \u0026lt;xsd:attribute name=\u0026#34;json-handler\u0026#34; default=\u0026#34;GSON\u0026#34;\u0026gt; \u0026lt;xsd:annotation\u0026gt; \u0026lt;xsd:documentation\u0026gt; \u0026lt;![CDATA[The enum value of java: io.vanslog.spring.data.meilisearch.config.JsonHandlerBuilder. The default is GSON.]]\u0026gt; \u0026lt;/xsd:documentation\u0026gt; \u0026lt;/xsd:annotation\u0026gt; \u0026lt;xsd:simpleType\u0026gt; \u0026lt;xsd:restriction base=\u0026#34;xsd:string\u0026#34;\u0026gt; \u0026lt;xsd:enumeration value=\u0026#34;GSON\u0026#34;\u0026gt; \u0026lt;xsd:annotation\u0026gt; \u0026lt;xsd:documentation\u0026gt; \u0026lt;![CDATA[Use GSON as the JSON handler.]]\u0026gt; \u0026lt;/xsd:documentation\u0026gt; \u0026lt;/xsd:annotation\u0026gt; \u0026lt;/xsd:enumeration\u0026gt; \u0026lt;xsd:enumeration value=\u0026#34;JACKSON\u0026#34;\u0026gt; \u0026lt;xsd:annotation\u0026gt; \u0026lt;xsd:documentation\u0026gt; \u0026lt;![CDATA[Use JACKSON as the JSON handler.]]\u0026gt; \u0026lt;/xsd:documentation\u0026gt; \u0026lt;/xsd:annotation\u0026gt; \u0026lt;/xsd:enumeration\u0026gt; \u0026lt;/xsd:restriction\u0026gt; \u0026lt;/xsd:simpleType\u0026gt; \u0026lt;/xsd:attribute\u0026gt; \u0026lt;xsd:attribute name=\u0026#34;client-agents\u0026#34; type=\u0026#34;xsd:string\u0026#34;\u0026gt; \u0026lt;xsd:annotation\u0026gt; \u0026lt;xsd:documentation\u0026gt; \u0026lt;![CDATA[The comma delimited string array of client agents.]]\u0026gt; \u0026lt;/xsd:documentation\u0026gt; \u0026lt;/xsd:annotation\u0026gt; \u0026lt;/xsd:attribute\u0026gt; \u0026lt;/xsd:extension\u0026gt; \u0026lt;/xsd:complexContent\u0026gt; \u0026lt;/xsd:complexType\u0026gt; \u0026lt;/xsd:element\u0026gt; \u0026lt;/xsd:schema\u0026gt; 엘리먼트 및 속성 위 XSD는 meilisearch-client 엘리먼트를 정의하고 있습니다. 해당 엘리먼트는 다음과 같은 속성을 가지고 있습니다.\nhost-url Meilisearch 서버의 주소를 지정합니다. 기본값은 http://localhost:7700입니다. api-key Meilisearch 서버의 API 키를 지정합니다. json-handler JSON을 처리하는 라이브러리를 지정합니다. 기본값은 GSON입니다. client-agents Meilisearch 클라이언트의 에이전트를 지정합니다. 기본값은 빈 배열입니다. NamespaceHandler public class MeilisearchNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser(\u0026#34;meilisearch-client\u0026#34;, new MeilisearchClientBeanDefinitionParser()); } } 앞선 XSD에서 정의한 meilisearch-client 엘리먼트를 MeilisearchNamespaceHandler를 통해 등록했습니다. 이때 함께 등록한 MeilisearchClientBeanDefinitionParser는 Bean 등록에 필요한 속성을 파싱하는 역할을 합니다. 뒤에서 자세히 다루겠습니다.\nhttp\\://www.vanslog.io/spring/data/meilisearch=io.vanslog.spring.data.meilisearch.config.MeilisearchNamespaceHandler 이후 MeilisearchNamespaceHandler를 spring.handlers 파일에 등록하여 Spring에서 해당 핸들러를 사용할 수 있도록 했습니다.\nBeanDefinitionParser public class MeilisearchClientBeanDefinitionParser extends AbstractBeanDefinitionParser { @Override protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition( MeilisearchClientFactoryBean.class); setLocalSettings(element, builder); return getSourcedBeanDefinition(builder, element, parserContext); } private void setLocalSettings(Element element, BeanDefinitionBuilder builder) { Assert.hasText(element.getAttribute(\u0026#34;api-key\u0026#34;), \u0026#34;The attribute \u0026#39;api-key\u0026#39; is required.\u0026#34;); builder.addPropertyValue(\u0026#34;hostUrl\u0026#34;, element.getAttribute(\u0026#34;host-url\u0026#34;)); builder.addPropertyValue(\u0026#34;apiKey\u0026#34;, element.getAttribute(\u0026#34;api-key\u0026#34;)); builder.addPropertyValue(\u0026#34;clientAgents\u0026#34;, element.getAttribute(\u0026#34;client-agents\u0026#34;)); String jsonHandlerName = element.getAttribute(\u0026#34;json-handler\u0026#34;); Assert.isTrue(JsonHandlerBuilder.contains(jsonHandlerName), \u0026#34;JsonHandler must be one of \u0026#34; + Arrays.toString(JsonHandlerBuilder.values())); JsonHandlerBuilder handlerBuilder = JsonHandlerBuilder.valueOf(jsonHandlerName.toUpperCase()); builder.addPropertyValue(\u0026#34;jsonHandler\u0026#34;, handlerBuilder.build()); } private AbstractBeanDefinition getSourcedBeanDefinition( BeanDefinitionBuilder builder, Element source, ParserContext context) { AbstractBeanDefinition definition = builder.getBeanDefinition(); definition.setSource(context.extractSource(source)); return definition; } } Meilisearch 클라이언트를 생성하는데 필요한 정보는 4가지입니다. 그래서 hostUrl, apiKey, clientAgents, jsonHandler를 파싱하여 MeilisearchClientFactoryBean이 Client 객체를 생성할 수 있도록 했습니다.\nFactoryBean MeilisearchClientFactoryBean은 FactoryBean을 상속한 클래스로 Meilisearch 클라이언트를 생성하고 Spring Bean으로 등록하는 역할을 합니다.\npublic final class MeilisearchClientFactoryBean implements FactoryBean\u0026lt;Client\u0026gt;, InitializingBean { @Nullable private String hostUrl; @Nullable private String apiKey; @Nullable private JsonHandler jsonHandler; private String[] clientAgents; @Nullable private Client client; private MeilisearchClientFactoryBean() { this.clientAgents = new String[0]; } @Override public Client getObject() { return client; } @Override public Class\u0026lt;? extends Client\u0026gt; getObjectType() { return Client.class; } @Override public void afterPropertiesSet() throws Exception { Config config = new Config(hostUrl, apiKey, jsonHandler, clientAgents); client = new Client(config); } public void setHostUrl(String hostUrl) { this.hostUrl = hostUrl; } public void setApiKey(String apiKey) { this.apiKey = apiKey; } public void setJsonHandler(JsonHandler jsonHandler) { this.jsonHandler = jsonHandler; } public void setClientAgents(String[] clientAgents) { this.clientAgents = clientAgents; } } BeanDefinitionParser가 파싱한 속성값들을 이용하여 MeilisearchClientFactoryBean의 afterPropertiesSet 메소드로 Meilisearch 클라이언트를 생성합니다.\n마치며 네임스페이스를 직접 정의하고, BeanDefinitionParser와 FactoryBean으로 스프링 Bean을 등록하는 로직을 구현해봤습니다. 이러한 과정에서 스프링의 내부 동작 방식에 대해 이해할 수 있었고, 앞으로 진행할 기능 구현에서 또 어떤 경험을 할 수 있을 지 기대됩니다.\n","permalink":"https://vanslog.io/ko/posts/project/spring-data-meilisearch/support-configuration-with-namespace/","summary":"스프링 라이브러리에서 네임스페이스 기반 설정을 지원하는 방법","title":"Spring Data Meilisearch #1 - 네임스페이스 기반 설정"},{"content":"프로젝트 동기 최근 진행중인 Spring Data Meilisearch 프로젝트에서 Meilisearch 인스턴스에 접근하는 테스트 코드 작성이 필요했습니다. 다양한 방법을 고려하였지만 결국 Testcontainers를 사용하는 것이 가장 적합하다고 판단하였습니다. 하지만 Testcontainers에서 공식적으로는 Meilisearch를 지원하지 않았고, GenericContainer라는 클래스를 오버라이딩해야 했습니다. 그래서 이번 기회에 해당 기능을 제공하는 라이브러리를 직접 개발해보기로 결심하였고, Testcontainers Meilisearch 프로젝트를 시작하게 되었습니다.\n프로젝트 소개 Testcontainers Meilisearch는 현재 Testcontainers 공식 사이트에 등록되어 있으며, Maven Central 레포지토리에도 업로드되어 있습니다. 간단한 의존성 설정만으로 손쉽게 Meilisearch 컨테이너를 동작시키고 테스트 코드를 수행할 수 있습니다.\n의존성 설정 테스트 코드 상에서 Testcontainers Meilisearch를 사용하기 위해서는 다음과 같이 의존성을 추가해야 합니다.\nGradle testImplementation \u0026#39;io.vanslog:testcontainers-meilisearch:1.0.0\u0026#39; Maven \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;io.vanslog\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;testcontainers-meilisearch\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0.0\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; 사용법 예를 들어 Meilisearch에 접근하는 로직이 필요한 테스트 코드를 작성한다면, 다음과 같은 방법으로 Testcontainers Meilisearch를 사용할 수 있습니다.\n@Testcontainers class MeilisearchContainerTest { @Container private final MeilisearchContainer container = new MeilisearchContainer() .withMasterKey(\u0026#34;masterKey\u0026#34;); @Test void shouldConnectToMeilisearch() throws MeilisearchException { Config config = new Config( \u0026#34;http://\u0026#34; + container.getHost() + \u0026#34;:\u0026#34; + container.getMappedPort(7700), \u0026#34;masterKey\u0026#34; ); Client client = new Client(config); assertThat(client.isHealthy()).isTrue(); } 위 예제 코드는 간단하게 Meilisearch 클라이언트를 생성해서 테스트컨테이너와 통신이 되는 지 확인합니다. 여기서 주의할 점은 테스트 컨테이너는 포트를 랜덤하게 바인딩하므로, getMappedPort()를 통해 포트를 가져와야 한다는 점입니다. MeilisearchContainer는 7700 포트를 사용하여 컨테이너를 실행하므로, getMappedPort(7700)을 통해 포트를 가져올 수 있습니다.\nMaster Key를 지정하지 않을 경우 랜덤으로 생성됩니다. 따라서 반드시 클라이언트와 연결을 위해서 설정해주셔야 합니다.\n프로젝트 후기 이전까지는 자바의 패키지 생태계에 대한 이해가 부족하였지만, 이번 프로젝트를 통해 이러한 부분을 보완할 수 있었습니다. Sonatype OSSRH에 가입하고 Maven 빌드 설정에 연동하였고, 패키징과 GPG 서명을 한 이후 Maven Central에 배포하는 과정을 직접 경험해보았습니다. 복잡한 과정이었지만 Sonatype OSSRH Guide를 참고하면서 하나씩 진행해나갈 수 있었습니다. 이번 경험을 통해 앞으로도 다양한 자바 기반의 라이브러리 개발을 진행할 수 있으리라 기대합니다.\n해당 프로젝트는 아직 초기 단계이므로 기능이 많이 부족한 상태입니다. 앞으로 지속적으로 개선해나갈 생각이며, 혹시라도 직접 기여를 해주실 분이 계시다면 해당 Github 저장소에 이슈나 PR을 남겨주시면 감사하겠습니다. 🤗\n","permalink":"https://vanslog.io/ko/posts/project/testcontainers-meilisearch/","summary":"Testcontainers Meilisearch를 소개하고, 개발하면서 느낀 점을 공유합니다.","title":"Meilisearch를 위한 Testcontainers 라이브러리 개발기"},{"content":"오픈소스 검색엔진 동향 Meilisearch에 관심을 가지게 된 계기는 오픈소스 검색엔진의 동향을 살펴보는 과정에서 유의미한 데이터를 발견했기 때문입니다. 아래 그래프는 OSS Insight에서 발췌한 데이터로, 2011년부터 2021년까지 Github에 등록된 검색 엔진 프로젝트의 스타 수를 나타낸 것입니다.\nElasticsearch는 가장 많이 사용하는 검색엔진답게 지속적으로 1위를 유지하고 있습니다. 정말 흥미로운 점은 Meilisearch가 2018년에 등장한 이후 꾸준한 성장세를 보여주었으며, 현재 2위를 차지하고 있다는 점입니다.\nMeilisearch vs Elasticsearch Elasticsearch는 대규모 데이터 처리에 특화되어 있으며, Kibana와 통합하여 데이터 시각화를 하는 등 데이터 분석에 강점을 가지고 있습니다. 반면, Meilisearch는 최종 사용자 경험에 초점을 맞추고 있습니다. 강력한 오타 검색 기능과, 직관적인 사용자 경험 그리고 빠른 검색 속도를 제공합니다.\nMeilisearch Elasticsearch Source code licensing\tMIT (Fully open-source) SSPL (Not open-source) Built with Rust Java Data storage Disk with Memory Mapping Disk with RAM cache Meilisearch 공식 사이트에서 Elasticsearch와 비교한 내용을 표로 간단히 정리해보았습니다. Meilisearch는 빠른 응답을 제공하기 위해 Rust로 작성되었으며, 데이터 스토리지로 디스크와 메모리 매핑을 사용합니다. 이러한 특징으로 인해 Elasticsearch보다 빠른 검색 속도를 제공합니다.\nMeilisearch가 항상 Elasticsearch보다 항상 우수한 것은 아니지만, 전자 상거래, 문서 및 콘텐츠 검색과 같은 사이트나 인앱 검색에 적용하기 적합합니다.\nSpring 통합의 어려움 Meilisearch는 최종 사용자를 겨냥한 검색엔진인 만큼, 다양한 언어의 클라이언트 라이브러리를 제공하고 있습니다. 하지만 Spring과 직접 통합하는 방법은 제공하고 있지 않다는 사실을 깨달았습니다. 그렇기 때문에 현재는 자바 클라이언트를 사용하여 직접 쿼리를 작성해야 하는 번거로움이 있습니다.\nSpring Data 라이브러리 Spring은 Spring Data라는 프로젝트를 통해 다양한 데이터 스토리지와 쉽게 통합할 수 있는 인터페이스를 제공합니다. Elasticsearch나 Solr와 같은 검색엔진은 모두 Spring Data 라이브러리가 제공되고 있습니다.\nMeilisearch를 Spring에서 편리하게 사용할 수 있도록 하기 위해 Spring Data의 구현체를 제공하는 것이 가장 좋은 방법이라고 생각했습니다. 그래서 시작한 것이 Spring Data Meilisearch 프로젝트입니다.\nSpring Data Meilisearch의 목표 Meilisearch를 Spring 프로젝트에 쉽게 통합하여 사용할 수 있도록 하는 것이 목적입니다. 이를 위해서는 다음과 같은 기능이 필요합니다.\nRepository 인터페이스 제공 다양한 쿼리에 대응되는 메소드 제공 Meilisearch 클라이언트와 연동 Spring 네임스페이스를 통한 설정 제공 애노테이션을 통한 설정 제공 CDI를 통한 설정 제공 등등.. 마치며 프로젝트는 이제 막 시작되었고 할 일 투성이입니다. 하지만 유용한 솔루션을 직접 개발하는 것은 늘 즐거운 일이라고 생각합니다. 평소 자바 라이브러리를 직접 만들어보고 싶었는데, 이번 기회가 Spring Data 라이브러리의 내부 구조를 이해하는 좋은 계기가 될 것 같습니다.\n참고문헌 OSS Insight - Search Engine Meilisearch - comparison to alternatives ","permalink":"https://vanslog.io/ko/posts/project/spring-data-meilisearch/introduction/","summary":"Spring Data Meilisearch 프로젝트를 시작하게 된 계기와 목표에 대해 소개합니다.","title":"Spring Data Meilisearch #0 - 프로젝트 소개"},{"content":"Stack 대신 Deque를 사용하라? 평소에 자바를 사용해 알고리즘 문제를 풀면서 Stack 대신 Deque를 사용하곤 했습니다. 구글링을 하다 우연히 Stack 클래스 대신 Deque를 사용하라는 글을 자주 마주한 것이 계기였습니다.\n하지만 Deque를 사용해야 되는 이유에 대해 깊은 생각없이 막연히 사용하고 있던 것 같아, 이번 기회에 Stack과 Deque의 차이점을 자세히 알아보았습니다.\nA more complete and consistent set of LIFO stack operations is provided by the Deque interface and its implementations, which should be used in preference to this class. For example:\nDeque\u0026lt;Integer\u0026gt; stack = new ArrayDeque\u0026lt;Integer\u0026gt;(); stack.push(1); 자바 공식 문서에서는 Stack 클래스에 대해 다음과 같이 이야기합니다. Stack 클래스를 사용하기 보다는 Deque 인터페이스와 그 구현체를 사용하라고 말합니다.\n이번 포스팅에서는 Queue와 Stack 그리고 Deque에 자세히 살펴보고, Deque가 Stack 대비 가지는 장점이 무엇인지에 대해 알아보겠습니다.\nQueue/Stack/Deque Queue Queue는 FIFO(First In First Out)의 특징을 가지고 있는 자료구조입니다. 자바에서는 java.util.Queue\u0026lt;E\u0026gt; 인터페이스를 통해 해당 자료형을 제공합니다.\n제공 메서드 Throws exception Returns special value 삽입 add(e) offer(e) 삭제 remove() poll() 조회 element() peek() Queue는 데이터 조작에 필요한 메서드를 두 개씩 제공합니다. 어떤 메서드를 사용하느냐에 따라 예외 상황이 발생했을 때, 에러를 발생시킬 지 Special Value를 리턴할 지가 결정됩니다.\noffer 메서드 삽입이 제대로 이루어진 경우 true 반환 삽입이 제대로 이루어지지 않은 경우 false 반환 poll과 peek 메서드 데이터가 존재하지 않는 경우 null 값을 반환 사용 예제 Queue\u0026lt;Integer\u0026gt; queue = new LinkedList\u0026lt;\u0026gt;(); queue.offer(1); Queue는 보통 구현체로 LinkedList를 사용합니다.\nStack Stack은 LIFO(Last In First Out)의 특징을 가지고 있는 자료구조입니다. 자바에서는 java.util.Stack\u0026lt;E\u0026gt; 클래스를 통해 해당 자료형을 제공합니다. 이는 Vector 클래스를 상속받아 구현되어 있습니다.\n제공 메서드 Stack 메서드 삽입 push(e) 삭제 pop() 조회 peek() 사용 예제 Stack\u0026lt;Integer\u0026gt; stack = new Stack\u0026lt;\u0026gt;(); Stack은 그 자체로 구현체이므로 다음과 같이 사용합니다.\nDeque Deque는 Double Ended Queue의 약자로, 흔히 양방향 큐라고 불리는 것입니다. 자바에서는 java.util.Deque\u0026lt;E\u0026gt; 인터페이스를 통해 해당 자료형을 제공합니다. 이는 Queue 인터페이스를 상속받아 구현되어 있습니다.\n제공 메서드 First Element (Head) Last Element (Tail) Throws exception Special value Throws exception Special value 삽입 addFirst(e) offerFirst(e) addLast(e) offerLast(e) 삭제 removeFirst() pollFirst() removeLast() pollLast() 조회 getFirst() peekFirst() getLast() peekLast() 양방향으로 데이터를 삽입하고 삭제하는 기능을 제공합니다. Queue와 동일하게 Special Value를 리턴하는 메서드와 에러를 발생하는 메서드로 분리되어 있습니다.\nStack/Queue 메서드 vs. Deque 메서드 Queue 메서드 대응되는 Deque 메서드 add(e) addLast(e) offer(e) offerLast(e) remove() removeFirst() poll() pollFirst() element() getFirst() peek() peekFirst() Deque는 Queue 인터페이스를 상속한 것이므로 Queue의 메서드를 모두 제공합니다. 위 표는 Queue의 메서드와 Deque의 메서드를 비교한 것입니다.\nStack 메서드 대응되는 Deque 메서드 push(e) addFirst(e) pop() removeFirst() peek() peekFirst() Deque는 Stack의 메서드 또한 모두 제공합니다. 위 표는 Stack의 메서드와 Deque의 메서드를 비교한 것입니다.\nStack/Queue의 메서드를 호출하면 벌어지는 일 그런데 문득 궁금해졌습니다. Deque는 Queue나 Stack의 메서드 어떻게 함께 지원하고 있는 것인지가 말이죠. 그래서 인텔리제이를 사용해서 실제 자바 코드를 살펴보았습니다.\nDeque의 구현체인 ArrayDeque 클래스는 Stack의 메서드를 다음과 같이 제공하고 있습니다. push, pop, peek 메서드와 같이 Stack의 메서드를 사용하면 이것에 대응되는 Deque의 메서드를 호출하도록 위임하는 것입니다. 이는 Queue의 메서드에 대해서도 마찬가지로 적용되어 있습니다.\n사용 예제 Deque\u0026lt;Integer\u0026gt; deque = new ArrayDeque\u0026lt;\u0026gt;(); deque.addFirst(1); Queue\u0026lt;Integer\u0026gt; queue = new ArrayDeque\u0026lt;\u0026gt;(); queue.offer(1); Deque\u0026lt;Integer\u0026gt; stack = new ArrayDeque\u0026lt;\u0026gt;(); stack.push(1); Queue의 경우 Deque의 부모 인터페이스이므로, 데이터 타입을 Queue로 선언할 수 있습니다.\n결론적으로 Deque는 양방향 큐로써 접근하고 사용할 수도 있지만, Stack이나 Queue처럼 사용할 수도 있습니다. 이는 곧 기존에 Stack과 Queue를 사용하던 코드를 Deque로 변경하더라도 문제가 없다는 것을 의미합니다.\n그 아버지에 그 아들 Stack 클래스는 앞서 잠시 언급했듯이 Vector 클래스를 상속하여 구현되어 있습니다. Vector 클래스는 List를 구현한 클래스로 자바 1.0부터 제공되어 왔습니다. 하지만 현재는 성능 상의 문제로 사용이 권장되지 않고 있습니다. 결과적으로 Stack 클래스도 사용이 권장되지 않게 된 것이죠.\nVector의 사용이 권장되지 않는 이유 비교 Vector ArrayList 동기화 처리 O X 쓰레드 안전 O X 성능 비교적 느림 비교적 빠름 용량 증가 2배 1.5배 Vector는 동기화된 메서드로 구성되어 있어 멀티 쓰레드 환경에서 안전합니다. 하지만 단일 쓰레드 환경에서도 동기화 처리에 대한 오버헤드가 발생하여 성능 저하가 발생할 수 있습니다. 따라서 단일 쓰레드 환경에서는 ArrayList를 사용하는 것이 성능상 유리합니다.\n자바 1.5 이후부터는 Collections.synchronizedList가 제공되어 ArrayList 클래스를 사용하더라도 동기화 처리를 할 수 있습니다. 따라서 쓰레드 환경에 무관하게 ArrayList를 사용하는 것이 좋습니다. 실제로 오늘날 자바를 사용하는 대부분의 프로젝트에서는 Vector를 사용하지 않고 ArrayList를 사용하고 있습니다.\nStack의 사용이 권장되지 않는 이유 비교 Stack ArrayDeque 동기화 처리 O X 쓰레드 안전 O X 성능 비교적 느림 비교적 빠름 표를 보시면 쉽게 눈치채시겠지만, Stack과 ArrayDeque의 관계는 Vector와 ArrayList의 관계와 매우 유사합니다. 다시 말해 Stack을 사용하면 안되는 이유는 Vector를 사용하면 안되는 이유와 동일하다고 볼 수 있는 것이죠.\n하지만 Deque는 별도의 동기화 처리를 위한 메서드가 없습니다. 이를테면 Collections.synchronizedDeque와 같은 메서드가 제공되지 않는다는 것이죠. 그렇다면 어떻게 멀티 쓰레드 환경에서 안전하게 사용할 수 있을까요?\nclass SyncStack\u0026lt;E\u0026gt; { private final Deque\u0026lt;E\u0026gt; stack = new ArrayDeque\u0026lt;\u0026gt;(); public synchronized void push(E e) { stack.push(e); } } 이러한 문제는 외부 동기화를 통해 해결할 수 있습니다. 위 예시와 같이 동기화 처리를 외부에서 해주면 Deque를 멀티 쓰레드 환경에서도 안전하게 사용할 수 있습니다.\n마치며 이번 기회에 자바 자료구조에 대해 자세히 살펴보는 시간을 가질 수 있었습니다. 무작정 사용하라고 해서 사용하기보다는 제대로 이해하고 이용할 수 있는 개발자가 되어야겠다는 다짐을 하였습니다.\n요약 Stack은 Vector를 상속받아 구현되어 있다. Vector는 단일 쓰레드 환경에서의 성능 저하로 사용이 권장되지 않는다. 따라서 Vector를 상속한 Stack 또한 사용하지 않는 것이 좋다. ArrayDeque는 Deque의 구현체이며 Stack과 Queue의 메서드를 모두 지원한다. Stack 대신 ArrayDeque를 사용하면 성능 향상을 기대할 수 있다. 쓰레드 안전한 ArrayDeque를 사용하기를 원한다면 외부 동기화를 사용하자. 참고문헌 Java 공식 문서 - Stack Java 공식 문서 - Vector Java 공식 문서 - Deque Java 공식 문서 - Collections.synchronizedList ArrayList vs Vector 동기화 \u0026amp; 성능 차이 비교 ","permalink":"https://vanslog.io/ko/posts/language/java/why-use-deque-instead-of-stack/","summary":"\u003ch2 id=\"stack-대신-deque를-사용하라\"\u003eStack 대신 Deque를 사용하라?\u003c/h2\u003e\n\u003cp\u003e평소에 자바를 사용해 알고리즘 문제를 풀면서 \u003ccode\u003eStack\u003c/code\u003e 대신 \u003ccode\u003eDeque\u003c/code\u003e를 사용하곤 했습니다.\n구글링을 하다 우연히 \u003ccode\u003eStack\u003c/code\u003e 클래스 대신 \u003ccode\u003eDeque\u003c/code\u003e를 사용하라는 글을 자주 마주한 것이 계기였습니다.\u003c/p\u003e\n\u003cp\u003e하지만 \u003ccode\u003eDeque\u003c/code\u003e를 사용해야 되는 이유에 대해 깊은 생각없이 막연히 사용하고 있던 것 같아,\n이번 기회에 \u003ccode\u003eStack\u003c/code\u003e과 \u003ccode\u003eDeque\u003c/code\u003e의 차이점을 자세히 알아보았습니다.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eA more complete and consistent set of LIFO stack operations is provided by the Deque interface and its implementations, which should be used in preference to this class. For example:\u003c/p\u003e","title":"[Java] 왜 Stack 대신 Deque를 사용하는가?"},{"content":"개요 스프링 부트 애플리케이션을 배포하는 방법에는 크게 두 가지가 있습니다.\nJAR 파일을 빌드하여 Amazon EC2에 배포 컨테이너 이미지를 빌드하여 Amazon ECS에 배포 두 가지 방식의 차이는 빌드 결과물의 형식에 있으며, 이러한 결과물에 따라 배포되는 환경이 달라집니다. 인터넷에 나와 있는 많은 레퍼런스를 보면 대개 JAR 파일을 빌드하여 Amazon EC2에 배포하는 방법을 소개하고 있습니다.\n하지만 이러한 방법보다는 Amazon ECS에 배포하는 방법이 프로젝트를 관리하는 데 있어서 더욱 효과적입니다. PaaS 서비스인 Amazon ECS를 사용하면 자체적으로 서버를 관리해주기 때문에 서버 관리에 대한 부담이 줄어들기 때문입니다.\n그래서 저는 최근에 DND에서 진행한 웨딩맵 프로젝트를 Amazon ECS에 배포하였고 Jib과 GitHub Actions를 사용하여 배포 파이프라인을 구축하였습니다. 이번 포스팅에서는 제가 구축한 Amazon ECS 배포 자동화 방식에 대해 소개해드리고자 합니다.\n배포 자동화 구축 Overview Amazon ECS 배포 자동화 구축을 본격적으로 시작하기에 앞서, 배포 과정을 단계별로 정리해보겠습니다.\nflowchart LR a1[GitHub Actions] --\u003e a2[Jib] --\u003e |Build Container Image| a3[Amazon ECR] a1 --\u003e a4[Task Definition] a4 --\u003e |Deploy to ECS| a5[Amazon ECS] a4 --\u003e |Container Image| a3 GitHub Actions에서 Jib을 사용하여 컨테이너 이미지를 빌드한다. 빌드된 이미지를 Amazon ECR에 푸시한다. Task Definition에 정의된 컨테이너 이미지를 Amazon ECS에 배포한다. 위 단계를 보고 이해가 되지 않는 부분이 있더라도 걱정하지 않으셔도 좋습니다. 아래에서 각 단계에 대해 자세히 알아볼 예정입니다.\nAmazon ECR 이미 Amazon ECR을 사용하여 컨테이너 이미지를 관리하고 있다면 다음 챕터로 넘어가셔도 좋습니다.\nAmazon ECS에 애플리케이션을 배포하기 위해서는 우선 컨테이너 이미지를 준비해두어야 합니다. 컨테이너 이미지는 Docker Hub나 Amazon ECR와 같은 컨테이너 레지스트리 서비스를 활용하여 관리합니다. 이번 포스팅에서는 Amazon ECR을 사용하는 방법을 기준으로 설명하겠습니다.\n💡 Docker Hub와 Amazon ECR의 차이는 Docker Hub는 공개 레포지토리를 제공하고 Amazon ECR은 비공개 레포지토리를 제공한다는 점입니다. 서비스의 공개를 원치 않는다면 Amazon ECR을 사용하는 것이 좋은 선택입니다.\n레포지토리 생성 컨테이너 이미지를 저장하기 위해서는 레포지토리를 생성해야 합니다. Amazon ECR 콘솔에 접속하여 리포지토리 생성 버튼을 클릭하면 아래와 같은 화면이 나타납니다.\n레포지토리 이름을 입력하고 하단의 리포지토리 생성 버튼을 한 번 더 클릭합니다.\n이렇게 리포지토리를 생성하면 위와 같이 URI가 생성됩니다. 이러한 URI는 컨테이너 이미지를 빌드하고 업로드하는데 사용됩니다. 바로 다음 챕터에서 사용될 것이므로 복사해두시기 바랍니다.\n💡 레포지토리의 이름은 컨테이너 이미지의 이름과 동일하게 설정해야 합니다. 도커 허브에서 이미지를 받아올 때 지정하는 이름이 이것과 동일한 개념입니다.\nJib Jib은 Google에서 제공하는 Java용 Docker 이미지 빌더입니다. Jib을 사용하면 Dockerfile을 작성하지 않아도 컨테이너 이미지를 빌드할 수 있습니다.\nDocker를 사용하여 컨테이너 이미지를 빌드하는 경우 위와 같은 과정을 거칩니다. Dockerfile을 작성해야 하는 번거로움과 여러 단계의 빌드 과정을 거쳐야 한다는 단점이 있습니다.\n반면 Jib을 사용하면 위와 같은 과정을 거치지 않고 컨테이너 이미지를 빌드할 수 있으며, 바로 컨테이너 레지스트리에 푸시할 수도 있습니다.\nGradle Plugin 설정 Jib을 사용하기 위해서는 Gradle 또는 Maven의 플러그인을 추가해야 합니다. 이번 포스팅에서는 Gradle을 사용하는 것을 기준으로 설명하겠습니다.\n플러그인 추가 Jib 플러그인을 build.gradle에 추가합니다.\nplugins { id \u0026#39;com.google.cloud.tools.jib\u0026#39; version \u0026#39;3.1.4\u0026#39; } Jib 설정 jib { from { image = \u0026#39;eclipse-temurin:17-jdk-alpine\u0026#39; } to { image = \u0026#39;\u0026lt;aws_account_id\u0026gt;.dkr.ecr.\u0026lt;region\u0026gt;.amazonaws.com/\u0026lt;repository_name\u0026gt;\u0026#39; tags = [\u0026#39;latest\u0026#39;, \u0026#34;${project.version}\u0026#34;.toString()] credHelper = \u0026#39;ecr-login\u0026#39; } container { creationTime = \u0026#39;USE_CURRENT_TIMESTAMP\u0026#39; jvmFlags = [\u0026#39;-Dspring.profiles.active=prod\u0026#39;, \u0026#39;-XX:+UseContainerSupport\u0026#39;, \u0026#39;-Dserver.port=8080\u0026#39;, \u0026#39;-Dfile.encoding=UTF-8\u0026#39;] ports = [\u0026#39;8080\u0026#39;] user = \u0026#39;nobody:nogroup\u0026#39; } } 해당 설정에서 가장 중요하게 볼 부분은 to 설정의 image입니다. 이전 목차에서 Amazon ECR에 레포지토리를 생성했다면, image에 Amazon ECR 레포지토리의 URI를 image에 붙여넣어주시면 됩니다.\n로컬에서 직접 레지스트리에 푸시하기 위해서는 credHelper 설정과 인증 툴을 설치가 필요합니다. 이와 관련된 자세한 설정 내용은 Jib 공식 문서를 통해 확인해주세요. 모든 설정이 완료했다면 ./gradlew jib 명령어로 이미지를 빌드하고 레지스트리에 푸시할 수 있습니다.\nAmazon ECS 이미 Amazon ECS 기반으로 애플리케이션을 운영중이라면 다음 챕터로 넘어가셔도 좋습니다.\nAmazon ECS를 배포하기 위해서는 몇 가지 개념에 대해 잡고 가는 것을 추천드립니다. 특히 클러스터, 서비스, 태스크 정의에 대해서는 간략하게라도 이해하고 넘어가는 것이 좋습니다. 저는 처음에 이러한 개념을 이해하지 않은 채로 Amazon ECS 배포를 진행하는 과정에서 되려 많은 시간을 낭비했는데, 이 글을 보시는 여러분은 그러지 않기를 바랍니다.\n클러스터 클러스터는 Amazon ECS에서 컨테이너를 관리하는 단위입니다. Amazon ECS 콘솔 \u0026gt; 클러스터 탭 \u0026gt; 클러스터 생성에서 태스크를 생성할 수 있습니다.\n클러스터 생성 과정에서 가장 중요한 부분은 인프라 설정입니다. Amazon ECS에는 EC2와 Fargate 두 가지 방식으로 클러스터를 생성할 수 있습니다. Fargate는 서버리스 컨테이너 관리 서비스로, EC2보다 사양 대비 비용이 저렴하지는 않지만 시간 단위로 비용을 지불하므로 더욱 유연하게 사용할 수 있습니다.\n상황에 따라 결정하시면 되지만 프리티어를 사용하기 위해 EC2를 선택했습니다. 운영체제로는 Amazone Liuux 2를 선택하고 인스턴스 유형은 t2.micro를 사용하였습니다.\n태스크 정의 태스크 정의는 Amazon ECS에서 컨테이너 실행에 필요한 이미지나 사양 등을 정의하는 단위입니다. Amazon ECS 콘솔 \u0026gt; 태스크 정의 탭 \u0026gt; 새 태스크 정의 생성에서 태스크를 생성할 수 있습니다.\n이때 새 태스크 정의 생성과 JSON을 사용하여 새 태스크 정의 생성 두 가지 방법 중 하나를 선택할 수 있는데, 간편하게 진행하기 위해서는 새 태스크 정의 생성을 선택하시면 됩니다.\n태스크 정의 패밀리에는 새로 생성할 태스크 정의 이름을 입력하고, 아래 컨테이너 항목들을 설정하면 됩니다. 컨테이너에서 중요하게 볼 부분은 이미지의 URI입니다. Jib 설정에 사용한 레포지토리 URI를 그대로 사용하면 되며, 필요에 따라 태그를 지정할 수 있습니다.\n서비스 서비스는 Amazon ECS에서 컨테이너를 실행하는 단위이며 태스크 정의를 기반으로 생성합니다. Amazon ECS 콘솔 \u0026gt; 클러스터 탭 \u0026gt; 생성한 클러스터 선택 \u0026gt; 서비스 탭 \u0026gt; 생성에서 서비스를 생성할 수 있습니다.\n서비스 생성 과정에서 중요하게 볼 부분은 태스크 정의를 설정하는 것입니다. 이전 챕터에서 생성한 태스트 정의 패밀리를 선택하고 개정을 클릭하여 원하는 버전의 태스크 정의를 선택할 수 있습니다.\n태스크 정의 파일 내려받기 Amazon ECS에서 생성한 태스크 정의를 Github Actions에서 사용하기 위해서는 JSON 형식으로 된 설정 파일이 필요합니다. AWS CLI를 사용하면 쉽게 태스크 정의 파일을 내려받을 수 있습니다.\naws ecs describe-task-definition \\ --task-definition {task-definition-family} \\ --query taskDefinition \u0026gt; task-definition.json 해당 명령어를 프로젝트 루트 경로에서 실행하면 task-definition.json 파일이 생성됩니다. 이렇게 생성된 파일은 Github Actions에서 사용할 수 있도록 GitHub 레포지토리에 커밋해주세요.\nGitHub Actions 이 챕터에서는 Github Actions를 통해 Amazon ECR에 이미지를 푸시하고, Amazon ECS에 배포하는 파이프라인을 구축합니다. Jib, Amazon ECR, Amazon ECS에 대한 설정을 모두 완료하지 않았다면 이전 챕터를 참고하여 완료해주세요.\nname: CD on: push: branches: - main jobs: deploy: name: Deploy to AWS runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: \u0026lt;AWS_REGION\u0026gt; - name: Login to Amazon ECR uses: aws-actions/amazon-ecr-login@v1 - name: Set up JDK 17 uses: actions/setup-java@v2 with: java-version: 17 distribution: \u0026#39;temurin\u0026#39; cache: \u0026#39;gradle\u0026#39; - name: Build and push image to Amazon ECR run: ./gradlew clean jib -x test - name: Deploy to AWS ECS uses: aws-actions/amazon-ecs-deploy-task-definition@v1 with: task-definition: task-definition.json cluster: \u0026lt;ECS_CLUSTER_NAME\u0026gt; service: \u0026lt;ECS_SERVICE_NAME\u0026gt; wait-for-service-stability: true 위 코드는 Jib을 사용하여 애플리케이션을 빌드하고 Amazon ECR에 이미지를 푸시한 후, Amazon ECS에 배포하는 GitHub Actions 파이프라인입니다. 이때 AWS_ACCESS_KEY_ID와 AWS_SECRET_ACCESS_KEY이 Github Secrets에 등록되어 있어야 하는데, 이는 AWS에 접근하기 위해 필요한 액세스 키입니다.\nAWS IAM 콘솔에서 AmazonEC2ContainerRegistryFullAccess와 AmazonECS_FullAccess 권한을 부여한 사용자를 생성하고, 액세스 키를 발급받아 Github Secrets에 등록해주세요.\n결과 확인 및 에러 해결 main 브랜치에 커밋이 발생하면 Github Actions가 애플리케이션을 빌드하고 Amazon ECS에 새로운 버전을 배포합니다. 하지만 위 이미지와 같이 Warning 메시지가 발생할 수도 있는데 이는 task-definition.json 파일에 정의하지 않아도 되는 항목이 포함되어 있기 때문입니다. task-definition.json 파일에서 이러한 항목들을 제거해주면 Warning 메시지를 없앨 수 있습니다.\n마치며 저는 웨딩맵 프로젝트를 진행하는 과정에서 위와 같은 파이프라인을 구축하였습니다. 덕분에 새로운 기능을 프로덕션 환경에 배포하는 과정을 간소화할 수 있었고, 서버 관리에 대한 부담을 덜 수 있었습니다. 이것이 바로 저희 팀이 애플리케이션 개발에 몰두할 수 있었던 비결입니다.\n참고 문헌 SpringBoot + Gradle jib + Github Actions + ECR + ECS 파이프라인 구축하기 ","permalink":"https://vanslog.io/ko/posts/devops/automate-ecs-deployments-with-jib-and-github-actions/","summary":"개발에 집중하기 위한 배포 파이프라인 구축","title":"Jib과 GitHub Actions를 사용한 ECS 배포 자동화"},{"content":"지난 토요일(3/4)에 최종 발표를 마치는 것으로 DND에서 보낸 8주간의 여정이 끝났습니다. 사실 이번 활동을 통해 많은 것을 배웠지만, 한편으로는 아쉬운 점도 남았습니다. 이번 글을 통해 DND 8기 활동을 마치며 느낀 점을 정리해보려고 합니다.\n나만의 웨딩플래너, 웨딩맵 💍 프로젝트 소개 결혼을 준비할 때 언제, 어디서, 무엇을 준비할지 막막하지 않으신가요? 그렇다고 플래너나 업체를 동반 하자니 가격이 만만치 않죠 🤔 “예비부부들의 결혼 일정 준비를 도와주는 서비스가 없을까?” 이러한 고민 끝에 저희는 ‘나만의 웨딩플래너, 웨딩맵’을 만들게 되었어요. 프로젝트 기간 2022.01.08 ~ 진행중 관련 링크 피그마 프론트엔드 레포지토리 백엔드 레포지토리 백엔드 API 문서 협업 방식 🤝 커뮤니케이션 Slack, Discord 통한 소통 Figma를 통한 디자인 작업물 공유 GitHub Projects를 통한 이슈 관리 Notion을 통한 회의록 작성 팀 문화 개인적으로 이번 프로젝트를 진행하면서 만족스러웠던 중 하나는 팀 문화였습니다. 팀원들과 함께 매일 아침에 데일리 스크럼을 진행하였고, 매주 토요일에 KPT 회고를 진행했습니다.\n데일리 스크럼 데일리 스크럼에서는 서로의 업무 진행 상황과 오늘 할 일에 대해 간략히 공유하는 시간을 가졌습니다. 만약 업무 진행중 발생한 문제가 있다면 간략히 공유만 하였고, 필요에 따라 추가 회의를 진행하였습니다. 진행 시간은 15분으로 짧았지만 그 덕에 빠르게 서로의 업무를 이해할 수 있었습니다.\nKPT 회고 회고 방식 중 하나인 KPT(Keep/Problem/Try)를 사용하였습니다. 지난 한 주간의 팀 활동을 돌아보며 만족스러운 점 또는 아쉬운 점을 공유하였고 개선방안에 대해서 논의하였습니다. 덕분에 팀 내에서 발생한 문제를 빠르게 파악하고 해결할 수 있었고 팀원들 간의 신뢰도가 높아졌습니다.\n컨벤션 저희 팀은 여러 컨벤션을 정의하여 효율적으로 프로젝트를 관리하고 있습니다. 커밋 메시지와 브랜치 네이밍 컨벤션에 대해서는 프론트, 백엔드 모두 동일하게 아래와 같이 정의하였습니다.\n커밋 메시지: Conventional Commits 브랜치 네이밍: Git Flow 코딩 컨벤션의 경우 프론트엔드는 EsLint와 Prettier를 사용하여 검사 및 포매팅을 하고 있으며, 백엔드는 Checkstyle을 사용하여 검사하고 있습니다.\nCheckstyle의 경우 Google Java Style Guide를 따르도록 설정하였습니다.\n개인의 성장은 팀의 성장으로 📈 사이드 프로젝트의 장점 중 하나는 이전에 경험해보지 못한 것에 도전하는 것이라 생각합니다. 저는 이번 프로젝트에서 다양한 CI/CD 파이프라인을 구축하는 경험을 쌓았습니다. 하지만 이는 제 개인적인 성장뿐 아니라 프로젝트 자체의 퀄리티를 높이는데도 큰 도움이 되었습니다.\n테스트 커버리지 70% 달성 웨딩맵 백엔드의 소스코드는 테스트 커버리지 70% 이상을 유지하고 있습니다. 이러한 것이 가능했던 것은 SonarCloud와 연동하여 지속적으로 소스코드의 품질을 관리하였기 때문입니다. 새로운 기능이 머지되기 이전에 일정 커버리지 이하의 코드는 머지되지 않도록 설정하였고, 이를 통해 지속적으로 커버리지를 일정 수준 이상 유지할 수 있었습니다.\n실제 테스트 커버리지는 웨딩맵 SonarCloud 대시보드에서 자세히 확인하실 수 있습니다.\n웨딩맵 백엔드는 SonarCloud와 Checkstyle을 함께 연동하여 사용하고 있습니다. 해당 방법이 궁금하신 분은 SonarCloud와 Checkstyle을 통합하여 사용하기 글을 참고해주세요.\nAPI 문서화 웨딩맵의 백엔드 API 문서는 Spring Rest Docs를 사용하여 제공하고 있습니다. 테스트 코드를 작성함과 동시에 문서화를 진행하는 방식이므로 현재 제공하는 기능에 대해서만 문서화가 이루어집니다. 항상 최신화된 API 문서를 제공하는 덕분에 프론트엔드 팀과의 소통에도 큰 도움이 되었습니다.\n문서화 페이지는 현재 GitHub Pages를 사용하여 해당 주소로 배포하고 있습니다. 모든 배포 과정은 GitHub Actions를 사용하여 자동화되어 있습니다.\n더 자세한 내용은 공식 Actions를 활용한 GitHub Pages 배포 글을 참고해주세요.\nAmazon ECS 기반의 서버 배포 자동화 flowchart LR subgraph Service Architecture Front-End \u003c--\u003e|Request \u0026 Response| a1{Amazon ELB} a1 \u003c--\u003e a2[Amazon ECS] a2 \u003c--\u003e a3[(Amazon S3)] a2 \u003c--\u003e a4[(Amazon RDS)] a2 \u003c--\u003e a5[(Amazon ElastiCache)] end subgraph CD Pipeline b1[GitHub Actions] --\u003e b2[JIB] b2 --\u003e |Container Image Build \u0026 Push| b3[Amazon ECR] b3 --\u003e |Deploy to ECS| a2 end 웨딩맵은 Amazon ECS에 배포된 스프링 애플리케이션을 중심으로 운영되고 있습니다. main 브랜치에 변경사항을 머지하는 즉시 새로운 버전의 컨테이너가 빌드되고 배포되는 CD 파이프라인을 구축하였습니다.\nAmazon ECS 배포 자동화에 대한 자세한 내용은 추후 포스팅에 작성할 예정입니다.\n배포 파이프라인을 구축한 덕분에 서버 배포 주기가 매우 빨라졌고, 새로운 기능을 빠르게 제공할 수 있게 되었습니다. 또한 Amazon ECS를 통해 서비스를 제공하면서 직접 EC2의 서버를 관리하는 것보다 안정적인 서비스 제공이 가능하였습니다.\nAmazon ECS의 경우 배포된 컨테이너의 상태를 모니터링하고, 이상이 발생할 경우 자동 재시작해주는 기능을 제공합니다. 또한 Blue/Green 배포를 기본적으로 지원하므로 서비스의 가용성을 높일 수 있습니다.\n아쉬운 점 📝 서비스 정식 배포 저희 서비스는 정식 배포를 못한 상태로 DND 8기를 마무리하였습니다. 서비스 운영 경험을 쌓고 싶었던 저에게는 많이 아쉬운 결과였습니다. 하지만 저희 팀은 모두 여전히 서비스를 완성시키는 것에 목표를 두고 있습니다. 비록 DND 활동은 공식적으로 끝났지만 4월까지 서비스를 완성하고 배포할 계획입니다.\n누락된 예외처리 짧은 기간 내 서비스를 개발하다 보니 누락된 예외처리 로직이 많았습니다. 예외로직이 있더라도 서버 상에서 에러만 출력하고 프론트엔드에 응답하지 않는 경우도 있었습니다. 기능 개발을 진행하면서 누락된 예외처리를 추가할 수 있도록 수정할 예정입니다.\n테스트 코드도 코드다 테스트 코드 또한 유지보수 관리 대상이며 퀄리티를 신경써야 합니다. 하지만 테스트 커버리지를 높이기 위한 노력에 치중한 나머지 퀄리티에는 다소 소홀했습니다. 앞으로는 기능을 개발하는 과정에서 테스트 코드의 퀄리티도 신경쓸 수 있도록 해야겠다는 생각이 들었습니다.\n마치며 기획부터 개발까지 모든 과정에 참여한 프로젝트가 처음이라 서툴렀지만 많은 것을 배울 수 있는 시간이었습니다. 특히 애자일 방법론의 스크럼이나 회고를 제대로 사용해볼 수 있었다는 점이 가장 만족스러웠습니다. 이전에 학교 팀프로젝트에서는 이러한 과정이 제대로 이루어지지 않았기 때문에 더욱 그 의미가 크게 느껴졌습니다.\nDND 활동을 마친 후 웨딩맵과 이전 프로젝트 사이에 어떤 차이가 있었는지 되새겨봤습니다. 마침내 내린 결론은 의사소통의 투명성에 있었다고 생각합니다. 학교에서 진행한 프로젝트에서는 자신의 의견을 제시하고 팀원들의 의견을 듣는 과정이 제대로 이루어지지 않았습니다. 그래서 팀원들 간의 의견 충돌이 잦았고 서로가 생각하는 프로젝트의 방향성이 달랐습니다. 그 결과 프로젝트는 처참하게 실패했습니다.\n저에게 있어서 이번 프로젝트의 경험은 이전의 실패를 극복하기 위한 기회이기도 하였습니다. 좋은 팀원들과 운영진분들이 함께한 덕분에 얻을 수 있는 경험이었다고 생각합니다. 너무나 감사한 마음으로 DND 활동을 마무리하고, 웨딩맵 프로젝트 최종 완성까지 끝까지 노력해보겠습니다. 🚀\n","permalink":"https://vanslog.io/ko/posts/retrospective/dnd/dnd-8th-weddingmap-project-retrospective/","summary":"DND 8기 활동을 마치며","title":"[DND 8기] 웨딩맵 프로젝트 회고"},{"content":"DND에서 진행하는 프로젝트에서 Spring Rest Docs를 사용하여 API 문서화를 진행하고 있으며, 이렇게 만들어진 문서화 페이지는 GitHub Pages를 통해 배포하고 있습니다. GitHub Pages는 워낙 잘 알려진 서비스이므로 이를 통해 배포하는 방법은 다양한 자료들이 존재합니다. 하지만 공식 액션을 사용하는 방법은 아직까지 잘 알려지지 않은 것 같아 이를 소개하고자 합니다.\nGitHub Pages GitHub Pages는 GitHub에서 제공하는 정적 웹사이트 호스팅 서비스입니다. 무료로 제공되는 서비스이므로 간단한 웹사이트를 호스팅하기에 적합합니다. 사이드 프로젝트를 진행하는 과정에서 프론트 팀과 API 문서를 공유하기에 적합합니다.\nBranch를 통한 배포 일반적으로 gh-pages라는 이름의 브랜치를 통해 배포하는 방식을 사용합니다. 페이지의 정적 파일을 해당 브랜치에 업로드하는 것으로 배포를 진행합니다. 이번 포스팅에서는 GitHub Actions를 사용하여 배포하는 방법을 중점적으로 소개할 예정이므로 브랜치를 이용한 배포에 대한 자세한 정보는 GitHub Pages 공식 문서를 참고하시기 바랍니다.\nGitHub Actions를 사용한 배포 최근 GitHub는 GitHub Pages를 배포하기 위해 Actions을 사용하는 방법을 추가하였습니다. 액션을 사용하여 페이지를 배포하는 것의 장점은 배포하는 작업을 쉽게 자동화할 수 있으며, 별도의 브랜치를 생성하지 않아도 된다는 점입니다. 이전까지 비공식적으로 지원하던 액션인 peaceiris/actions-gh-pages를 사용중이셨다면 공식 액션으로 마이그레이션하는 것을 고려해보시기 바랍니다.\n💡 해당 기능은 베타 버전이므로 프로젝트 성격에 따라 사용 여부를 결정하시기 바랍니다.\n구성 요소 GitHub Actions를 사용한 GitHub Pages 배포 방법은 다음과 같은 액션들이 사용됩니다.\nactions/configure-pages GitHub Pages를 배포하기 위한 환경을 설정 actions/upload-pages-artifact GitHub Pages를 배포하기 위한 빌드 결과물을 업로드 actions/deploy-pages GitHub Pages 배포 예제 코드 아래 예제는 Spring REST Docs를 사용하여 API 문서를 생성하고 GitHub Pages에 배포하는 예제입니다. GitHub Starter Workflows에서 제공하는 코드를 기반으로 작성되었습니다.\nname: API Docs on: push: branches: - main pull_request: permissions: # 권한 설정 contents: read # for actions/configure-pages pages: write # for actions/deploy-pages id-token: write # for actions/deploy-pages concurrency: # 동시 실행 제한 group: \u0026#34;pages\u0026#34; cancel-in-progress: true jobs: deploy: name: API Docs environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} # 배포된 페이지의 URL runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Set up Pages uses: actions/configure-pages@v2 - name: Set up JDK 17 uses: actions/setup-java@v2 with: java-version: 17 distribution: \u0026#39;zulu\u0026#39; cache: \u0026#39;gradle\u0026#39; - name: Build Asciidoc run: ./gradlew asciidoctor - name: Upload pages artifact uses: actions/upload-pages-artifact@v1 with: path: \u0026#39;./build/docs/asciidoc\u0026#39; - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v1 에러 해결 Branch \u0026ldquo;\u0026lt;브랜치명\u0026gt;\u0026rdquo; is not allowed to deploy to github-pages due to environment protection rules.\nGitHub Pages를 배포하는 과정에서 다음과 같은 에러가 발생할 수 있습니다. 이는 특정 브랜치에 대해서 배포가 제한되어 있는 경우 발생합니다. 이를 해결하는 방법은 프로젝트 내 Settings \u0026gt; Environments \u0026gt; github-pages \u0026gt; Deployment branches 에서 허용할 브랜치를 지정하거나 All branches를 선택해주시면 됩니다.\n마치며 지금까지 공식 GitHub Actions를 사용하여 GitHub Pages에 배포하는 방법에 대해 알아보았습니다. 이러한 방법을 사용하여 스프링 부트의 API 문서를 생성하고 GitHub Pages에 배포하는 과정을 자동화할 수 있었습니다.\n참고 문헌 GitHub Pages 공식 문서 actions/configure-pages actions/upload-pages-artifact actions/deploy-pages GitHub Starter Workflows ","permalink":"https://vanslog.io/ko/posts/devops/deploy-github-pages-with-actions/","summary":"공식 Actions를 활용한 GitHub Pages 배포 방법을 소개합니다.","title":"공식 Actions를 활용한 GitHub Pages 배포"},{"content":"최근 DND라는 대외활동을 통해 사이드 프로젝트의 백엔드 개발을 맡고 있습니다. 본격적인 프로젝트 시작에 앞서 CI/CD 파이프라인을 구축하는 작업을 진행하고 있습니다.\n스프링 기반의 백엔드 코드를 정적 분석하기 위해 SonarCloud와 Checkstyle을 연동하는 작업을 진행하였습니다. 이 글에서는 왜 이러한 결정을 내리게 되었는지, 그리고 연동은 어떻게 이루어지는지에 대해 다루도록 하겠습니다.\nSonarCloud SonarQube는 버그, 코드 스멜, 보안 취약점을 발견하기 위해 사용하는 정적 분석 도구입니다. 저장소에 코드가 푸시되거나 PR이 생성되면 SonarQube가 코드를 분석하여 결과를 보여주는 기능을 제공합니다. 다만 SonarQube를 사용하기 위해서는 이것을 구동하는 서버를 운영해야 한다는 단점이 있습니다.\n직접 서버를 운영하기에는 무리가 있다는 생각에 SonarCloud를 대신 사용하기로 결정하였습니다. SonarCloud는 SonarQube를 클라우드 서비스로 제공하는 서비스로, GitHub과 같은 코드 저장소와 연동을 지원하며 Public 저장소에 대해서는 무료로 사용할 수 있기 때문입니다.\nSonarCloud 단점 SonarCloud는 SonarQube에 비해 제한된 기능을 제공합니다. 대표적으로 코드를 작성할 때 지켜야 하는 규칙인 코딩 컨벤션을 체크해주는 Linter 기능이 기본적으로 제공되지 않습니다. 코드의 가독성을 높이고, 코드의 일관성을 유지하기 위해 Linter를 도입하는 것은 필수적입니다. 그러므로 SonarCloud를 사용하면서도 Checkstyle를 함께 사용하기로 결정하게 되었습니다.\n외부 분석기 리포트 연동 SonarCloud는 External Analyzer Reports와 연동하는 기능을 제공합니다. 해당 기능은 외부 분석기의 결과를 SonarCloud에 전달하여 함께 분석할 수 있도록 해줍니다. 해당 기능을 이용하면 Checkstyle을 SonarCloud와 연동하여 사용할 수 있습니다.\nSonarCloud와 Checkstyle 연동하기 이번 목차에서는 SonarCloud와 Checkstyle의 연동을 진행한 방법에 대해 자세히 소개해드리도록 하겠습니다. 해당 설명에서는 Gradle 기반 프로젝트에 GitHub Actions를 함께 사용하는 경우를 가정하고 있습니다. 만약 다른 기술 스택을 사용한 프로젝트에 적용하고 싶으시다면 해당 글과 SonarCloud 공식 문서를 함께 참고하시기 바랍니다.\nSonarCloud Auto Analysis 비활성화 SonarCloud는 기본적으로 Auto Analysis라는 기능이 활성화되어 있습니다. 이 기능은 저장소의 코드 품질을 자동으로 분석해주는 기능입니다. 이 기능은 매우 편리하게 사용할 수 있다는 장점이 있으나, 외부 정적 분석 도구를 함께 사용하기 위해서는 이 기능을 비활성화해야 합니다.\n비활성화하는 방법은 저장소와 연동된 SonarCloud 프로젝트의 Settings에서 General 탭을 선택한 후 Auto Analysis를 비활성화하는 것입니다.\n만약 본인이 SonarCloud의 어드민이 아니라면 해당 기능을 비활성화할 수 없습니다. 이 경우 어드민에게 요청하여 비활성화를 요청하시기 바랍니다.\nCI 기반 분석 설정 이제 CI 기반 분석을 설정해야 합니다. CI 기반 분석이란 GitHub Actions이나 Travis CI와 같은 CI 툴을 사용하여 SonarCloud에 분석 결과를 전달하는 방법을 말합니다.\nSonarQube 플러그인 설정 SonarCloud는 SonarQube 플러그인을 사용하여 분석을 수행하기 때문에 SonarQube 플러그인을 설정해야 합니다.\nplugins { id \u0026#39;org.sonarqube\u0026#39; version \u0026#39;3.5.0.2730\u0026#39; } sonar { properties { property \u0026#39;sonar.host.url\u0026#39;, \u0026#39;https://sonarcloud.io\u0026#39; property \u0026#39;sonar.organization\u0026#39;, \u0026#39;dnd-side-project\u0026#39; property \u0026#39;sonar.projectKey\u0026#39;, \u0026#39;dnd-side-project_dnd-8th-8-backend\u0026#39; } } 위 예제는 저희 프로젝트의 build.gradle 파일에 설정한 내용입니다. sonar.organization과 sonar.projectKey는 SonarCloud에서 프로젝트를 생성한 후 프로젝트의 Settings에서 확인하여 작성해주시면 됩니다. sonar.host.url은 SonarQube를 동작시키는 호스트 URL로 SonarCloud를 사용한다면 https://sonarcloud.io로 작성하시면 됩니다.\nSonarQube 플러그인은 SonarQube와 SonarCloud 모두 호환됩니다. 만약 SonarQube를 사용한다면 호스트 URL만 SonarQube의 호스트 URL로 수정하면 됩니다.\n과거 버전에서는 sonarqube라는 이름으로 task를 설정하였지만 현재는 sonar라는 이름으로 task를 설정해야 합니다.\nGitHub Actions 설정 이번 목차에서는 GitHub Actions를 이용하여 SonarCloud에 분석 결과를 전달하는 예제를 살펴보겠습니다. 아래 예제는 저희 프로젝트의 .github/workflows/ci.yml 파일에 설정한 내용입니다.\nname: CI on: push: branches: - main pull_request: jobs: sonarcloud: name: SonarCloud runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up JDK 17 uses: actions/setup-java@v2 with: java-version: 17 distribution: \u0026#39;zulu\u0026#39; - name: Cache Gradle packages uses: actions/cache@v3 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles(\u0026#39;**/*.gradle\u0026#39;) }} restore-keys: ${{ runner.os }}-gradle - name: Cache SonarCloud packages uses: actions/cache@v3 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - name: Build and analyze run: ./gradlew build jacocoTestReport sonar --info env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 위 예제는 main 브랜치에 푸시되거나 PR이 생성될 때 SonarCloud에 분석을 요청합니다. GITHUB_TOKEN과 SONAR_TOKEN은 GitHub Actions에서 사용할 토큰입니다. GITHUB_TOKEN은 별도로 생성할 필요가 없지만, SONAR_TOKEN은 SonarCloud에서 프로젝트의 Settings에서 생성하고 GitHub Secrets에 등록해야 합니다.\nGradle 프로젝트인 경우 sonarcloud-github-action을 사용하면 Gradle 플러그인을 사용하라는 에러가 발생하므로 반드시 위 예제와 같이 설정해야 합니다.\nCheckstyle 플러그인 설정 이번 목차에서는 Checkstyle 플러그인을 설정하는 방법을 소개합니다. 만약 Checkstyle 플러그인을 사용하지 않기를 원하신다면 이번 목차는 건너뛰어도 됩니다.\nplugins { id \u0026#39;checkstyle\u0026#39; } checkstyle { toolVersion = \u0026#34;10.4\u0026#34; } Checkstyle 플러그인은 build.gradle 파일에 설정합니다. 위 예제는 저희 프로젝트의 build.gradle 파일에 설정한 내용입니다. toolVersion은 Checkstyle 플러그인의 버전을 의미합니다.\nChecstyle 설정 파일 Checkstyle 플러그인을 사용하기 위해서는 checkstyle.xml 파일을 config/checkstyle 디렉토리에 위치시켜야 합니다. 보통 구글의 코딩 스타일에 맞추기 위해 구글이 제공하는 Checkstyle 설정 파일을 사용합니다. 프로젝트 성격에 따라서 썬 마이크로시스템즈의 Checkstyle 설정 파일을 사용하거나 직접 커스텀하여 사용할 수도 있습니다.\nCheckstyle 리포트 경로 연동 마지막으로 SonarCloud와 Checkstyle을 연동하는 예제를 소개합니다. 앞선 설정을 모두 완료했다면 매우 간단하게 연동할 수 있습니다. sonar.java.checkstyle.reportPaths라는 속성만 추가하면 됩니다.\nplugins { id \u0026#39;checkstyle\u0026#39; id \u0026#39;org.sonarqube\u0026#39; version \u0026#39;3.5.0.2730\u0026#39; } checkstyle { maxWarnings = 0 toolVersion = \u0026#34;10.4\u0026#34; } sonar { properties { property \u0026#39;sonar.host.url\u0026#39;, \u0026#39;https://sonarcloud.io\u0026#39; property \u0026#39;sonar.organization\u0026#39;, \u0026#39;dnd-side-project\u0026#39; property \u0026#39;sonar.projectKey\u0026#39;, \u0026#39;dnd-side-project_dnd-8th-8-backend\u0026#39; property \u0026#39;sonar.java.checkstyle.reportPaths\u0026#39;, \u0026#39;build/reports/checkstyle/main.xml\u0026#39; } } sonar.java.checkstyle.reportPaths는 Checkstyle 플러그인이 생성한 main.xml 파일을 SonarCloud에 전달합니다. 이렇게 설정하면 SonarCloud에서 Checkstyle 플러그인의 경고를 확인할 수 있습니다.\n마치며 이렇게 SonarCloud와 Checkstyle을 연동하여 코드 품질을 관리할 수 있습니다. 이번 글에서는 SonarCloud와 Checkstyle을 연동하는 방법을 소개했습니다. 하지만 다른 정적 분석 도구와도 연동할 수 있습니다. 예를 들어 JaCoCo를 사용한다면 sonar.jacoco.reportPaths 속성을 추가하면 됩니다. 만약 JaCoCo를 함께 연동하여 코드 커버리지를 관리하고 싶다면 Java test coverage를 참고하시면 됩니다.\n저희 프로젝트는 SonarCloud와 Checkstyle, JaCoCo를 연동하여 코드 품질을 관리하고 있습니다. 만약 설정 파일을 직접 보고 싶으시다면 저희 프로젝트의 GitHub Actions 설정 파일 혹은 build.gradle 파일을 참고해주세요.\n참고 문헌 SonarCloud 공식 사이트 SonarCloud 공식 문서 SonarCloud GitHub Action SonarCloud Github Action Sample - Gradle 예제 SonarCloud Gradle 플러그인 공식 문서 Checkstyle Gradle 플러그인 공식 문서 ","permalink":"https://vanslog.io/ko/posts/devops/interate-sonarcloud-with-checkstyle/","summary":"SonarCloud를 사용하면서 코딩 스타일을 체크하는 방법","title":"SonarCloud와 Checkstyle을 통합하여 사용하기"},{"content":"\n오랫동안 블로그 글 작성을 못하다가 DND 8기에 합격했다는 소식을 가지고 돌아왔습니다. 오늘 오리엔테이션에 참여한 이후 느낀 점들도 포함하여 작성해보도록 하겠습니다.\nDND란? DND는 개발자와 디자이너가 8주간 협업하는 사이드 프로젝트 경험을 제공하는 프로그램입니다. 세미나와 같은 교육 프로그램이 제공되기는 하나 기본적으로는 팀원들끼리 협업하며 프로젝트를 진행하는 것이 주 목적입니다. 따라서 사이드 프로젝트 경험을 쌓고 싶은 사람들에게 추천할 수 있는 프로그램입니다.\nDND에 지원한 이유 DND가 제게 부족한 점을 채워줄 수 있을 것이라는 생각이 들었기 때문입니다. 평소 스스로 부족한 부분이라고 느끼던 부분이 프로젝트 경험이 적다는 것이었는데, 이러한 점을 확실히 채울 수 있을 것이라는 생각이 들었습니다.\n특히 개발자와 디자이너가 협업할 수 있는 기회를 제공한다는 점이 매력적이었습니다. 교내에서 프로젝트를 진행하는 과정에서는 디자이너와 협업할 수 있는 기회가 없었습니다. 그렇기 떄문에 항상 프로젝트의 완성도가 떨어져서 아쉬움이 많았습니다. 그래서 이번 기회를 통해 디자이너와 협업할 수 있는 경험을 쌓고 싶었습니다.\n좋은 프로덕트를 만들어낸 팀들의 특징 개개인이 맡은 역할을 충실히 수행 개발 파트와 디자인 파트의 적극적인 협업 슬랙을 이용한 커뮤니케이션 도움이 필요한 경우 운영진의 도움을 적극적으로 받기 뚜렷한 목표 설정 매주 전달되는 과제를 성실히 수행 스크럼을 사용하여 진행상황 공유 이전 기수에서 좋은 프로덕트를 만들어낸 팀들의 특징들입니다. 이번 오리엔테이션에서 가장 중요하게 생각된 부분이라 따로 정리해보았습니다. 앞으로 DND를 진행하면서 이러한 특징들을 잘 살려서 프로젝트를 진행해보고자 합니다.\n마치며 사실 합격이라는 결과를 받았을 때는 놀랐습니다. 제가 생각했던 것보다 더 많은 사람들이 지원했고, 그 중에서 뽑는 인원이 매우 적었기 때문입니다. 쉽지 않은 기회를 주신만큼 최선을 다해 프로젝트를 진행하겠습니다. 감사합니다.\n","permalink":"https://vanslog.io/ko/posts/retrospective/dnd/dnd-8th-backend-developer-acceptance-review/","summary":"오리엔테이션에 참여한 이후 느낀 점들을 포함하여 DND 8기에 합격한 후기를 작성해보았습니다.","title":"[DND 8기] 백엔드 개발자 합격 후기"},{"content":"Git Hooks Git Hooks는 깃에 이벤트가 발생했을 때 실행되는 스크립트입니다. .git/hooks 디렉터리에 스크립트를 작성해서 사용하는데, 이를 통해 커밋 직전에 코드컨벤션을 검사하거나 테스트코드를 실행해볼 수 있습니다.\n문제점 혼자 개발하는 경우에는 이러한 방법이 나쁘지 않을 수도 있겠지만, 여럿이 개발에 참여하는 경우 아래와 같은 문제가 발생할 수 있습니다.\nhook 스크립트 공유의 어려움. 모두가 동일한 버전의 hook을 사용한다는 보장이 없음. pre-commit 적용 pre-commit은 이러한 문제를 쉽게 해결해주는 좋은 솔루션입니다. 프로젝트 내에 설정 파일을 통해 hooks의 버전을 관리할 수 있으며, 이것들을 손쉽게 로컬머신에 설치할 수 있습니다.\npre-commit 설치 $ pip install pre-commit # pip로 설치 $ brew install pre-commit # homebrew로 설치 설정 파일 생성 .pre-commit-config.yaml이라는 파일을 프로젝트 루트 경로에 생성합니다. 이후 필요한 hooks 목록을 아래와 같이 작성합니다.\nrepos: - repo: https://github.com/asottile/setup-cfg-fmt rev: v2.0.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder_python_imports rev: v3.8.3 hooks: - id: reorder-python-imports - repo: https://github.com/asottile/add-trailing-comma rev: v2.3.0 hooks: - id: add-trailing-comma 지원되는 hooks 목록은 해당 사이트에서 검색해볼 수 있습니다.\nhook 스크립트 설치 아래 명령어는 위에서 정의한 hooks를 실제로 .git/hooks 경로의 pre-commit 파일에 적용해줍니다.\n$ pre-commit install pre-commit 실행 로컬머신 위 과정을 거쳐 설정을 모두 마쳤다면 커밋을 할 때마다 지정한 hooks가 순차적으로 동작할 것입니다. 만약 커밋 없이 강제로 실행하고 싶다면 다음 명령어를 사용하면 됩니다.\n$ pre-commit run --all-files 위 이미지는 제가 checkstyle-cli라는 파이썬 패키지를 개발하는 과정에서 동작하는 화면입니다. 여러 linter나 formatter를 커밋을 할 때마다 동작시키는 과정을 통해 소스코드의 스타일을 일관되게 작성할 수 있습니다.\npre-commit.ci pre-commit.ci는 깃헙에 소스코드가 push될 때 pre-commit hook을 실행해주는 서비스입니다. 깃헙 레포지토리에 앱을 설치하여 사용할 수 있으며, .pre-commit-config.yaml 파일만 있다면 별도의 설정이 필요하지 않습니다.\n뛰어난 캐싱 성능 덕에 hook 실행속도가 다른 CI에 비해 매우 빠릅니다. 또한 주기적으로 설정파일 내에 있는 hook의 최신버전을 검사하여 봇이 PR을 날려주기 때문에 유지보수가 수월합니다.\nCI가 동작되면 위와 같은 페이지에서 결과를 확인할 수 있습니다. 깃헙 레포지토리에서 활용가능한 badge를 제공하고 있기 때문에 사이트 접근 없이도 CI 결과를 확인해볼 수도 있습니다.\n마치며 지금까지 pre-commit을 활용하여 쉽게 Git Hooks를 관리하는 방법에 대해서 알아보았습니다. 다음 포스팅에서는 pre-commit hook을 직접 만들어보고 배포하면서 겪은 일들에 대해서 공유해보도록 하겠습니다.\n","permalink":"https://vanslog.io/ko/posts/infra/easy-to-manage-git-hooks-with-pre-commit/","summary":"\u003ch2 id=\"git-hooks\"\u003eGit Hooks\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eGit Hooks\u003c/code\u003e는 깃에 이벤트가 발생했을 때 실행되는 스크립트입니다. \u003ccode\u003e.git/hooks\u003c/code\u003e 디렉터리에 스크립트를 작성해서 사용하는데, 이를 통해 커밋 직전에 코드컨벤션을 검사하거나 테스트코드를 실행해볼 수 있습니다.\u003c/p\u003e\n\u003ch3 id=\"문제점\"\u003e문제점\u003c/h3\u003e\n\u003cp\u003e혼자 개발하는 경우에는 이러한 방법이 나쁘지 않을 수도 있겠지만, 여럿이 개발에 참여하는 경우 아래와 같은 문제가 발생할 수 있습니다.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003ehook 스크립트 공유의 어려움.\u003c/li\u003e\n\u003cli\u003e모두가 동일한 버전의 hook을 사용한다는 보장이 없음.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"pre-commit-적용\"\u003epre-commit 적용\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://pre-commit.com\"\u003epre-commit\u003c/a\u003e은 이러한 문제를 쉽게 해결해주는 \u003cstrong\u003e좋은 솔루션\u003c/strong\u003e입니다. 프로젝트 내에 설정 파일을 통해 hooks의 버전을 관리할 수 있으며, 이것들을 손쉽게 로컬머신에 설치할 수 있습니다.\u003c/p\u003e","title":"pre-commit로 Git Hooks 쉽게 관리하기"},{"content":"스프링은 엔터프라이즈 환경을 위한 애플리케이션 프레임워크이며, 오늘날 자바 기반 웹 애플리케이션 개발의 표준입니다. 스프링을 완전히 이해하기 위해서는 이것의 등장 배경을 알고 있을 필요가 있습니다.\n왜냐하면 스프링은 엔터프라이즈 애플리케이션 개발을 위한 J2EE(Java 2 Platform, Enterprise Edition)의 문제점을 해결한 결과물이기 때문입니다. 스프링의 특징이나 장점들이 어떠한 배경 속에서 등장했는 지를 이해하면 더욱 스프링에 대한 이해가 높아질 수 있을 것입니다.\nJ2EE J2EE(Java 2 Platform, Enterprise Edition)는 엔터프라이즈 애플리케이션 개발을 위한 스펙의 집합입니다. 일반적으로 사용하는 자바인 J2SE(Java 2 Platform, Standard Edition)를 확장하여 만들어 졌습니다.\n💡 J2EE와 J2SE는 버전에 따라 명칭이 다르게 불립니다. 버전 5가 릴리즈된 시기에 Java EE와 Java SE로 명칭이 변경되었습니다. Java EE의 경우 오라클이 운영권을 이클립스 재단으로 이관하면서 Jakarta EE로 한 차례 더 변경되었습니다.\n스펙 J2SE APIs Servlet JSP EJB \u0026hellip; J2EE에는 기본적으로 앞서 말한 것과 같이 J2SE를 기반으로 하고 있으며, 엔터프라이즈 애플리케이션 개발에 필요한 요소들을 추가한 것입니다. 대표적으로 Servlet과 JSP, EJB가 있습니다. 한 번쯤 자바 기반의 웹 프로그래밍을 접해봤다면 들어는 보셨을 수도 있을 것입니다.\nApplication Server J2EE는 반드시 이것의 스펙을 만족하는 J2EE 애플리케이션 서버(J2EE Application Server)를 필요로 합니다. 이것을 통상적으로 WAS(Web Application Server)라고 했습니다. 대표적인 WAS로는 JEUS, Weblogic, WebSphere 등이 있습니다.\n💡 최근에는 자바뿐 아니라 Node.js나 Flask와 같이 다른 언어로도 백엔드 서버를 구축할 수 있으므로 더 이상 WAS == J2EE Applcation Server라는 말할 수는 없습니다.\n문제점 결정적으로 J2EE에서 Spring으로 패러다임이 변화하게 된 것에는 세 가지 이유가 있습니다.\n운영 비용 우선 J2EE는 운영환경의 요구사항이 매우 높았습니다. J2EE의 스펙을 모두 충족하는 풀스택 서버를 구축하기 위해서는 매우 고가의 서버가 요구되었고 운영 비용이 천문학적이었습니다.\n플랫폼 종속성 J2EE의 목적은 자바의 플랫폼 독립적인 특성을 활용하여, 환경에 종속적이지 않은 미들웨어를 제공하는 것이었습니다. 하지만 이러한 목적과 달리 실제 J2EE 애플리케이션은 플랫폼 종속적이었습니다.\n근본적으로 J2EE에 정의되지 않은 기능들을 많았던 것이 이유였습니다. 이는 결국 WAS의 제조사별로 구현방식이 달라지게 되었으며, 운영환경의 종속성을 탈피하려는 시도는 실패했습니다.\nEJB라는 겨울 EJB(Enterprise Java Bean)은 J2EE 환경에서 비즈니스 로직을 처리하는 분산 컴포넌트 기술입니다. 이것의 취지는 좋았으나 사용하기에 너무나도 복잡성이 높았습니다. Spring Framework의 개발자인 Rod Johnson은 J2EE Development without EJB라는 책에서 아래와 같이 말했습니다.\n\u0026ldquo;EJB와 같은 실패를 만들지 않기 위해서 가장 먼저 생각할 것은 기본 핵심가치(core values)인데 그것은 6가지로 구성된다.\u0026rdquo;\n- Simplicity - Scale down이 되지 않는 EJB같은 복잡한 아키텍처가 아니라 - 필요에 따라서 아키텍처 리팩토링에 의해서 scale up할 수 있는 심플한 아키텍처를 추구하는 것이 바람직하다. - Productivity - 실무에서 가장 중요한 것은 결국 생산성이고 그로 인한 비용절감\u0026amp;시간단축이 아니겠는가. - OO - Java는 그 자체로 훌륭한 OO언어이다. 왜 특정기술(EJB)등이 OO에 선행하여 좋은 OO디자인을 방해하는가? - Primacy of Requirement - 핵심요구사항에 집중하기. - Empirical Process - 스스로 확인하고 테스트하고 검증된 기술과 구조를 사용해야 한다. - IT계는 희한하게도 감정적인 유행도 많고 검증되지 않은 추측과 유언비어가 난무한다. - Testability - TDD니 TFD(Test First Development)등이 유행하고 있다. - 좀 지나치게 유행하는게 아닌가 싶기도 한데 그래도 그 중요성을 무시할 수 없다. Spring 스프링은 Spring Framework의 개발자 Rod Johnson이 J2EE Design and Development라는 책을 통해 기존 J2EE의 문제점을 지적한 것이 시작입니다. EJB 없이도 충분히 고품질의 확장 가능한 애플리케이션을 개발할 수 있음을 30,000줄짜리 예제 코드를 통해 증명했습니다.\n책 출간된 이후 Juergen Hoeller와 Yann Caroff는 오픈 소스 프로젝트를 제안하였고, 이것이 바로 오늘날의 스프링 프레임워크가 되었습니다. 스프링이라는 이름은 Yann Caroff가 제안한 것으로 J2EE라는 겨울을 거친 새로운 시작이라는 의미입니다.\n\u0026ldquo;the fact that Spring represented a fresh start after the “winter” of traditional J2EE\u0026rdquo;\n특징 내장 WAS 스프링은 J2EE 스펙을 모두 구현한 WAS가 필요하지 않습니다. 다시 말해 서블릿 컨테이너 기술만 구현된 WAS만으로도 충분히 동작합니다. 기본적으로 스프링은 톰캣(Tomcat)이나 제티(Jetty)를 내장하고 있으므로 별도의 WAS를 설치할 필요가 없습니다.\nPOJO POJO(Plain Old Java Object)는 일반적인 자바 객체를 말합니다. EJB와 같은 프레임워크에 종속되는 오브젝트를 사용하는 것에 대한 반발로 나온 단어로 마틴 파울러가 처음 만들었습니다.\nPOJO는 프레임워크 종속성 문제를 해결하기 위한 스프링의 핵심입니다. POJO 기반 프레임워크이므로 스프링을 사용하면 객체지향적이며 간결한 코드를 짤 수 있고 테스트를 쉽게 할 수 있습니다.\nJ2EE와의 관계 스프링을 공부하면서 가장 헷갈렸던 부분이 바로 J2EE와의 관계입니다. 검색 결과에 따라 스프링이 J2EE를 대체하는 기술이라고 설명하기도 하지만, J2EE 개발을 도와주는 프레임워크라고 소개되기도 합니다. 도저히 양립될 수 없는 두 가지 설명이 혼란을 가중시킵니다. 과연 어떤 것이 맞는 설명일까요?\n결론부터 말씀드리자면 스프링은 J2EE를 대체하는 기술이 맞습니다. 하지만 그렇다고 해서 J2EE와 전혀 관계가 없다고 하기에도 어렵습니다. 그 이유는 스프링은 J2EE 기술의 일부를 채용해서 만들어졌기 때문입니다. 대표적으로 서블릿은 여전히 스프링에서 통신하는 부분을 처리하기 위해 사용됩니다. 스프링이 J2EE 스펙을 전부 만족하는 것은 아니지만 일부 스펙을 만족하고 있다고는 할 수 있을 것 같습니다.\n제 개인적인 생각으로 이러한 오해가 시작된 것은 스프링을 개발한 Rod Johnson의 저서 J2EE Development without EJB 제목이 문제라고 생각합니다. EJB 없는 J2EE 개발이라는 말은 꼭 스프링이 J2EE 개발을 도와주는 것처럼 느껴집니다. 하지만 실제로는 EJB를 배제하고 J2EE 스펙의 일부만을 상속한 새로운 프레임워크의 등장을 말하고자 했던 것으로 보여집니다.\n마치며 스프링을 사용하면서 궁금했던 것들을 정리하다 보니 스프링이 등장하게 된 배경을 정리하게 되었습니다. 이를 통해 얼핏 알고 있던 스프링 이전의 기술들에 대해서 공부하는 시간을 가질 수 있었고, 스프링을 사용하는 이유와 장점에 대해 더 잘 알 수 있었습니다.\n여기서 언급하지 못한 내용들이 아직 많이 남아 있습니다. 스프링의 특징 중에 내장 WAS와 POJO만을 언급했는데, 이는 J2EE의 문제점과 대립되는 특징에 집중하기 위함이었습니다. 추후 포스팅을 통해 스프링의 다른 특징들과 POJO에 대해 자세한 내용으로 정리하겠습니다.\n참고 자료 Jakarta EE - Wikipedia 자바EE의 역사 및 스프링과의 관계 - okky Spring Boot을 언제 써야 할까? (2) 외장 WAS가 반드시 필요한가? - zepinos님 왜 EJB 없는 J2EE 개발에 대해 이야기하는가? - 토비님 Spring Framework: The Origins of a Project and a Name - Spring Blog ","permalink":"https://vanslog.io/ko/posts/web/spring/why-spring/","summary":"\u003cp\u003e스프링은 엔터프라이즈 환경을 위한 \u003ccode\u003e애플리케이션 프레임워크\u003c/code\u003e이며, 오늘날 자바 기반 웹 애플리케이션 개발의 표준입니다. 스프링을 완전히 이해하기 위해서는 이것의 \u003ccode\u003e등장 배경\u003c/code\u003e을 알고 있을 필요가 있습니다.\u003c/p\u003e\n\u003cp\u003e왜냐하면 스프링은 엔터프라이즈 애플리케이션 개발을 위한 \u003ccode\u003eJ2EE(Java 2 Platform, Enterprise Edition)\u003c/code\u003e의 문제점을 해결한 결과물이기 때문입니다. 스프링의 특징이나 장점들이 어떠한 배경 속에서 등장했는 지를 이해하면 더욱 스프링에 대한 이해가 높아질 수 있을 것입니다.\u003c/p\u003e\n\u003ch2 id=\"j2ee\"\u003eJ2EE\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eJ2EE(Java 2 Platform, Enterprise Edition)\u003c/code\u003e는 엔터프라이즈 애플리케이션 개발을 위한 스펙의 집합입니다. 일반적으로 사용하는 자바인 \u003ccode\u003eJ2SE(Java 2 Platform, Standard Edition)\u003c/code\u003e를 확장하여 만들어 졌습니다.\u003c/p\u003e","title":"[Spring] 왜 스프링인가"},{"content":"저는 최근 디지털배움터(정부가 추진하는 디지털 역량 교육 사업)의 강사로서 강의를 진행하고 있습니다. 처음 강사가 되고 한 고민은 어떤 강의를 진행하는 것이 좋을 지에 대한 것이었습니다. 해당 사업의 주된 목적이 디지털 취약 계층의 역량 강화에 있기 때문에 프로그래밍 수업은 수요가 많지 않았습니다.\n하지만 그렇다 할지라도 잘 하는 것을 수업으로 진행하는 게 맞다는 생각이 들었습니다. 프로그래밍 수업을 개설해봤지만 역시 예상대로 많은 수강생들이 모이지는 않았습니다. 다행하게도 친한 친구가 제게 웹 개발을 배우고 싶다고 해서 수업을 진행할 수 있었습니다.\n교육은 스스로의 성장 스프링 부트 기반 웹서비스 개발에 대해 강의를 진행하면서 친구에게서 생각치도 못한 부분에 대한 날카로운 질문을 받았습니다. 예를 들면 스프링에 의존성 주입이 사용되어야 하는 이유가 무엇이냐와 같은 질문이었습니다.\n질문을 답하는 과정에서 제 스스로가 의존성 주입에 대한 개념이 확실하게 잡혀져 있지 않다는 사실을 깨달았습니다. 아니 스프링 전반에 대한 개념이 부족함을 느꼈습니다. 제대로 알고 있었다면 설명을 제대로 할 수 있었겠지만 그렇지 못했습니다.\n결국에는 이에 대한 보충설명을 하기 위해 개념 공부를 하였고, 이 과정에서 저의 부족함을 보완할 수 있었습니다.\n\u0026ldquo;저는 교육이 아는 것을 일방적으로 알려주는 과정이라고 더 이상 생각하지 않습니다. 교육은 스스로를 되돌아보고 부족한 부분을 채울 수 있는 기회를 제공하는 것입니다.\u0026rdquo;\n블로그 글 작성의 필요성 학교 팀프로젝트 수업을 통해 스프링 부트를 사용해봤음에도 불구하고, 스프링의 기초적인 개념들에 대한 이해가 부족했습니다. 이 사실을 깨닫게 된 계기는 교육을 진행하는 과정에서 친구의 질문 덕분이었습니다. 하지만 저는 이와 동일한 효과를 블로그에 글을 적는 행위로도 가져올 수 있다고 생각합니다.\n블로그에 글을 작성하는 행위는 단순히 글을 적는 행위에 그치는 것이 아니기 때문입니다. 글을 적는 과정에서 스스로가 제대로 이해했는 지에 대해 생각하고, 이를 통해 아는 것과 모르는 것을 구별해낼 수 있습니다. 또한 모르는 것을 공부해서 아는 것으로 만들 수도 있습니다.\n\u0026ldquo;이러한 과정을 무수히 반복한다면 아는 것은 늘리고 모르는 것은 줄일 수 있게 됩니다.\u0026rdquo;\n이번 경험을 통해 블로그 글 작성의 중요성을 다시 한 번 느낄 수 있었고, 스프링과 관련된 개념들을 정리하며 완벽하게 이해하겠다는 목표를 세웠습니다.\n","permalink":"https://vanslog.io/ko/posts/general/what-i-learned-while-working-at-digital-competency-center/","summary":"\u003cp\u003e저는 최근 \u003ccode\u003e디지털배움터(정부가 추진하는 디지털 역량 교육 사업)\u003c/code\u003e의 강사로서 강의를 진행하고 있습니다. 처음 강사가 되고 한 고민은 어떤 강의를 진행하는 것이 좋을 지에 대한 것이었습니다. 해당 사업의 주된 목적이 \u003ccode\u003e디지털 취약 계층\u003c/code\u003e의 역량 강화에 있기 때문에 프로그래밍 수업은 수요가 많지 않았습니다.\u003c/p\u003e\n\u003cp\u003e하지만 그렇다 할지라도 잘 하는 것을 수업으로 진행하는 게 맞다는 생각이 들었습니다. 프로그래밍 수업을 개설해봤지만 역시 예상대로 많은 수강생들이 모이지는 않았습니다. 다행하게도 친한 친구가 제게 웹 개발을 배우고 싶다고 해서 수업을 진행할 수 있었습니다.\u003c/p\u003e","title":"디지털배움터에서 강사로 일하며 배운점"},{"content":"utterances는 개인 기술 블로그에서 많이 사용되는 오픈소스 댓글 서비스입니다. 깃헙 스타일의 깔끔한 디자인과 다크 모드를 지원한다는 특징을 가지고 있습니다.\n최근에는 나이트 모드와 다크모드를 다이나믹하게 변경할 수 있는 사이트가 많아지고 있습니다. 제 블로그 또한 이러한 방식이므로 utterances의 테마를 다이나믹하게 적용해야 했습니다.\n구글링을 해보니 이러한 방법에 대해서 언급하는 경우가 거의 없었습니다. 그래서 이번 포스팅에서는 utterances의 테마를 다이나믹하게 적용할 수 있는 방법을 중점으로 소개하려고 합니다.\nUtterances 적용 방법 단일 테마 적용 웹사이트가 하나의 테마만 적용될 때는 아래와 같은 일반적인 방법으로 댓글 기능을 적용하여 사용하시면 됩니다.\n댓글 기능을 적용하고자 하는 레포를 public으로 설정 깃헙에 utterances app 설치 utterances 사이트에 접속 repo와 theme를 지정해주고 하단에 위치한 스크립트 복사 댓글을 위치시킬 부분에 해당 코드를 붙여넣기 utterances app을 설치할 때, 모든 레포 혹은 댓글 기능이 필요한 레포만 지정하면 됩니다.\n다이나믹 테마 적용 다크모드와 나이트모드를 모두 지원하는 사이트의 경우, Utterances 또한 여기에 맞춰서 테마를 변경해주어야 합니다. 그렇지 않다면 디자인적인 통일성을 잃게 됩니다.\n댓글을 적용할 위치 선정 /layouts/partials/comments.html {{- /* Comments area start */ -}} \u0026lt;div class=\u0026#34;comments\u0026#34;\u0026gt; \u0026lt;script\u0026gt; \u0026lt;!-- 자바스크립트 작성 --\u0026gt; \u0026lt;/script\u0026gt; \u0026lt;/div\u0026gt; {{- /* Comments area end */ -}} 우선 댓글을 넣을 부분을 찾습니다. 제 블로그에 사용된 hugo의 경우 테마를 오버라이딩해서 사용할 수 있습니다. 테마를 제작하는 분들도 이러한 것을 고려하여 오버라이딩할 수 있도록 파일을 미리 만들어 둡니다. 그래서 저는 comments.html이라는 파일만을 오버라이딩하여 댓글 기능을 적용하였습니다.\n자바스크립트 코드 작성 loadComment(); const callback = (mutationsList) =\u0026gt; { mutationsList.forEach(mutation =\u0026gt; { if (mutation.attributeName === \u0026#34;class\u0026#34; \u0026amp;\u0026amp; document.querySelector(\u0026#39;.utterances-frame\u0026#39;)) { const message = { type: \u0026#39;set-theme\u0026#39;, theme: getTheme() }; const iframe = document.querySelector(\u0026#39;.utterances-frame\u0026#39;); iframe.contentWindow.postMessage(message, \u0026#39;https://utteranc.es\u0026#39;); } }) } const mutationObserver = new MutationObserver(callback); mutationObserver.observe(document.body, { attributes: true }); function getTheme() { // 사이트의 테마를 가져오는 메서드 var theme = window.localStorage \u0026amp;\u0026amp; window.localStorage.getItem(\u0026#34;pref-theme\u0026#34;); if (theme == null) { theme = window.matchMedia(\u0026#39;(prefers-color-scheme: dark)\u0026#39;).matches ? \u0026#39;dark\u0026#39; : \u0026#39;light\u0026#39;; } return theme === \u0026#39;dark\u0026#39; ? \u0026#39;github-dark\u0026#39; : \u0026#39;github-light\u0026#39;; } function loadComment() { let s = document.createElement(\u0026#39;script\u0026#39;); s.src = \u0026#39;https://utteranc.es/client.js\u0026#39;; s.setAttribute(\u0026#39;repo\u0026#39;, \u0026#39;junghoon-vans/vanslog\u0026#39;); // 본인 레포 지정 s.setAttribute(\u0026#39;issue-term\u0026#39;, \u0026#39;pathname\u0026#39;); s.setAttribute(\u0026#39;theme\u0026#39;, getTheme()); s.setAttribute(\u0026#39;crossorigin\u0026#39;, \u0026#39;anonymous\u0026#39;); s.setAttribute(\u0026#39;async\u0026#39;, \u0026#39;\u0026#39;); document.querySelector(\u0026#39;div.comments\u0026#39;).innerHTML = \u0026#39;\u0026#39;; document.querySelector(\u0026#39;div.comments\u0026#39;).appendChild(s); } 위 코드는 loadComments 메서드를 이용하여 utterances 댓글을 불러온 이후, mutationObserver가 DOM의 상태 변경을 인지하여 utterances의 테마를 변경시킵니다. getTheme 메서드는 현재 사이트가 어떤 모드인지를 확인해서 댓글에 적용할 테마를 지정합니다.\n제 블로그 테마인 papermod는 현재 모드에 대한 정보를 로컬스토리지에 pref-theme라는 이름으로 저장합니다. 그래서 이것을 통해 현재 다크 모드인지 라이트 모드인지를 확인하였습니다. 또한 테마를 직접 지정한 적이 없다면 로컬스토리지에 해당 값이 null일 수 있으므로, 이러한 경우 자동으로 시스템 설정을 따라가도록 하였습니다.\n혹시라도 이 글을 참고하여 본인 블로그에 적용하려 하시려는 분이 계시다면 다음을 유의하시면 됩니다. getTheme 메서드의 경우 사이트에 따라 색상 모드를 지정하는 방식이 다를 수 있으므로, 꼭 모드를 지정하는 변수가 어떤 것인지를 확인하셔야 합니다. 둘째로 loadCommant 메서드에서 레포지토리 지정을 본인의 것으로 변경해주셔야 합니다.\n마치며 블로그를 직접 커스텀하는 과정에서 프론트엔드 소스코드에 손을 대게 되었습니다. 이전까지 저의 영역은 백엔드에 한정되어 있다고 생각했는데, 막상 해보니 프론트엔드도 재미있다는 생각이 들었습니다. 자신의 한계를 제 스스로 설정하고 있었던 것은 아닌지에 대해서 생각을 하게 되는 계기가 된 것 같습니다. 앞으로는 조금 더 열린 마음으로 여러 방면으로 공부하는 마음가짐을 가져야 겠다는 생각을 하게 되었습니다.\n","permalink":"https://vanslog.io/ko/posts/general/apply-utterances-dynamic-theme/","summary":"\u003cp\u003e\u003ccode\u003eutterances\u003c/code\u003e는 개인 기술 블로그에서 많이 사용되는 오픈소스 댓글 서비스입니다. \u003ccode\u003e깃헙 스타일\u003c/code\u003e의 깔끔한 디자인과 \u003ccode\u003e다크 모드\u003c/code\u003e를 지원한다는 특징을 가지고 있습니다.\u003c/p\u003e\n\u003cp\u003e최근에는 나이트 모드와 다크모드를 \u003ccode\u003e다이나믹\u003c/code\u003e하게 변경할 수 있는 사이트가 많아지고 있습니다. 제 블로그 또한 이러한 방식이므로 \u003ccode\u003eutterances\u003c/code\u003e의 테마를 다이나믹하게 적용해야 했습니다.\u003c/p\u003e\n\u003cp\u003e구글링을 해보니 이러한 방법에 대해서 언급하는 경우가 거의 없었습니다. 그래서 이번 포스팅에서는 utterances의 테마를 다이나믹하게 적용할 수 있는 방법을 중점으로 소개하려고 합니다.\u003c/p\u003e\n\u003ch2 id=\"utterances-적용-방법\"\u003eUtterances 적용 방법\u003c/h2\u003e\n\u003ch3 id=\"단일-테마-적용\"\u003e단일 테마 적용\u003c/h3\u003e\n\u003cp\u003e웹사이트가 하나의 테마만 적용될 때는 아래와 같은 일반적인 방법으로 댓글 기능을 적용하여 사용하시면 됩니다.\u003c/p\u003e","title":"🔮 utterances 다이나믹 테마 적용하기"},{"content":"저는 배운 것을 정리하기 위해 기술 블로그를 시작했습니다. 하지만 학기중에 바쁘다는 이유로 블로그에 글을 업로드하는 주기가 길어졌습니다. 블로그에 글을 작성하려는 생각만 하고 이행하지 못하는 경우가 많아졌구요.\n이대로 블로그 운영에 손 놓고 있어서는 안되겠다는 생각에 해이해진 마음을 다잡기로 결심하였습니다. 블로그를 새로운 마음으로 다시 시작한다는 의미에서 블로그를 전체적으로 개편하였습니다.\n새로운 시작 블로그 테마 변경 저는 이 블로그를 2020년도 11월에 처음 hugo를 사용해서 만들었습니다. 그 당시 hugo를 사용하기로 결심한 이유는 golang 기반이므로 속도가 매우 빠르고 가벼웠기 때문입니다. 현재 hugo는 Next.js 다음으로 많이 사용하는 정적사이트 생성기입니다. (Jamstack 참조)\n그래서 블로그는 hugo 기반으로 유지하되, 블로그를 새롭게 시작한다는 마음에서 블로그의 테마를 변경하였습니다. 테마를 선정한 기준은 컨텐츠에 집중하게 하는 심플한 디자인이었습니다. 블로그의 핵심은 글에 있다고 생각하기 때문입니다.\n그러던 중 paper라는 테마가 눈에 들어왔습니다. 미니멀리즘한 디자인이 너무나 마음에 들었지만, 실제로 사용하기에 기능적으로 아쉬운 부분이 있었습니다. 그래서 이를 보완한 테마인 papermod를 적용하였습니다.\nGithub Pages에서 Vercel로 이전 기존에는 블로그 호스팅을 Github Pages를 통해서 하고 있었습니다. 하지만 최근 깃헙 서버 오류를 몇 차례 경험하면서 블로그를 호스팅하기에 불안정하다는 생각이 들었고, 타 서비스로의 마이그레이션을 결정하였습니다.\nnetlify와 Vercel 두 서비스 중에서 고민을 했는데, 국내 서비스 속도 측면에서 Vercel이 월등하다는 점을 알게 되어 Vercel을 사용하기로 결정하였습니다.\n배포를 하기 이전에 막연히 어려울 것이라는 생각을 가지고 있었습니다. AWS에 백엔드를 배포해본 경험은 있었지만 프론트엔드는 배포 경험이 없었기 때문입니다. 하지만 막상 실제 배포 과정은 깃헙 레포지토리 연동만으로 끝날 정도로 간단했습니다. 이를 통해 해보기 이전에 지레짐작해서 겁먹지 말자는 교훈(?)을 얻을 수 있었습니다.\n도메인 구매 예전부터 개인 블로그의 도메인을 구매하고 싶었는데 가격이 부담되서 미루고 있었습니다. 찾아보던 중 DreamHost라는 곳에서 io도메인을 29.99달러(처음 1년만, 이후는 39.95달러)에 제공하고 있다는 사실을 알게 되어 구매를 하였습니다.\nVercel에서 DNS 설정을 통해 간편하게 커스텀 도메인을 등록할 수 있었습니다. 이를 통해 도메인을 구매하고 적용하는 경험을 해볼 수 있었습니다.\n겉표지는 중요하지 않다 기록을 위한 도구라는 점에서 블로그와 노트는 별반 다르지 않습니다. 이 둘의 차이는 기록을 하는 공간이 디지털 세상이냐 아날로그 세상이냐의 차이죠.\n아날로그 세상에서 노트는 기록되지 않으면 가치를 잃고 버려집니다. 겉표지가 예쁘건 못생겼건 그것은 중요하지 않죠. 이와 마찬가지로 블로그도 디자인이 좋다고 할 지라도 기록되지 않는다면 의미가 없어진다고 생각합니다.\n이번 블로그 개편은 노트의 겉표지를 깔끔하게 바꿔주는 행동에 불과했습니다. 실제 블로그의 가치는 글에서 나온다는 신념 아래 꾸준히 글을 작성하도록 노력하겠습니다.\n","permalink":"https://vanslog.io/ko/posts/general/tech-blog-reorganization/","summary":"\u003cp\u003e저는 배운 것을 정리하기 위해 기술 블로그를 시작했습니다. 하지만 학기중에 바쁘다는 이유로 블로그에 글을 업로드하는 주기가 길어졌습니다. 블로그에 글을 작성하려는 생각만 하고 이행하지 못하는 경우가 많아졌구요.\u003c/p\u003e\n\u003cp\u003e이대로 블로그 운영에 손 놓고 있어서는 안되겠다는 생각에 해이해진 마음을 다잡기로 결심하였습니다. 블로그를 새로운 마음으로 다시 시작한다는 의미에서 블로그를 전체적으로 개편하였습니다.\u003c/p\u003e\n\u003ch2 id=\"새로운-시작\"\u003e새로운 시작\u003c/h2\u003e\n\u003ch3 id=\"블로그-테마-변경\"\u003e블로그 테마 변경\u003c/h3\u003e\n\u003cp\u003e저는 이 블로그를 2020년도 11월에 처음 \u003ccode\u003ehugo\u003c/code\u003e를 사용해서 만들었습니다. 그 당시 hugo를 사용하기로 결심한 이유는 \u003ccode\u003egolang\u003c/code\u003e 기반이므로 속도가 매우 빠르고 가벼웠기 때문입니다. 현재 \u003ccode\u003ehugo\u003c/code\u003e는 \u003ccode\u003eNext.js\u003c/code\u003e 다음으로 많이 사용하는 정적사이트 생성기입니다. (\u003ca href=\"https://jamstack.org/generators/\"\u003eJamstack\u003c/a\u003e 참조)\u003c/p\u003e","title":"기술 블로그 개편"},{"content":"테라($luna)는 몇 일 사이에 엄청난 상승을 보여주었습니다. 불과 3일 사이에 50달러에서 50% 이상 상승하여 80달러 수준으로 올라왔습니다. 이로 인해 암호화폐 시가총액이 9위였던 테라는 솔라나와 에이다를 제치고 7위가 되었습니다.\n이러한 움직임은 매우 인상적인데, 다른 암호화폐가 같은 기간동안 이 정도로 상승 압력이 크지 않았다는 점 때문입니다. 테라만 유독 큰 폭으로 상승하고 있습니다.\n테라🌎가 뭔데? 테라가 뭔지에 대해서 모르시는 분들을 위한 설명입니다. 이미 테라에 대한 이해가 있으신 분들은 넘어가셔도 좋습니다.\n테라는 흔히들 알고 있는 이더리움, BNB체인, 솔라나와 같은 레이어1 블록체인입니다. 가장 큰 특징은 알고리드믹 스테이블 코인 시스템을 기반으로 하는 블록체인이라는 점입니다.\n스테이블 코인은 뭐야? 스테이블 코인은 이름에서 알 수 있다싶이 안정적인 코인을 말합니다. 코인이 실생활에서 사용할 수 없는 점 중 하나가 큰 변동성입니다. 그래서 암호화폐는 화폐로서의 기능보다는 자산으로 보는 시각이 많습니다. 그래서 이러한 문제를 해결하고자 나온 것이 바로 달러와 동등한 가치를 갖는 테더(Tether)와 같은 스테이블 코인입니다. 해외 암호화폐 거래소에서 이용해보셨으면 무조건 한 번은 이용해보셨을 것입니다.\n법정화폐 담보형 스테이블 코인 법정화폐 담보형 스테이블 코인이란 각각의 회사가 고객들로부터 법정화폐(e.g. USD)를 받고, 이것을 담보로 발행하는 코인을 말합니다. 이 방식으로 스테이블 코인을 발행하면 생기는 문제는 과연 스테이블 코인 발행사를 신뢰할 수 있는가입니다. 2018년 경부터 테더사는 실제로 가지고 있는 달러 이상으로 테더를 발행했다는 의혹을 받았으며, 현재도 이러한 문제는 여전히 해결되지 않고 있습니다.\n알고리드믹 스테이블 코인 법정화폐 담보형 스테이블 코인의 불확실성으로 인한 문제를 푸는 방법은 스테이블 코인 또한 블록체인을 통해 투명하게 발행하면 됩니다. 하지만 탈중앙화로 코인의 가격을 일정하게 유지시키는 알고리즘을 개발하는 것은 쉬운 일이 아니라는 문제가 있습니다.\n테라의 등장 테라는 스테이블 코인인 테라와 이를 서포트하는 루나라는 두 종류의 코인으로 운영함으로서 알고리드믹 스테이블 코인을 구현하였습니다. 테라(UST)는 암호화폐 시장에서 인정받아 테더, 서클, 바이낸스USD에 이어 시총 4위(알고리드믹 스테이블 코인 중에서는 1위)에 위치하고 있습니다.\n테라: 각국의 법정화폐를 추종하는 스테이블 코인 달러의 가치를 추종하는 UST(USD Terra)와 원화와 대응되는 KRT(KRW Terra) 등 루나: 테라 블록체인 상에서 활용되는 코인이며 테라가 스테이블할 수 있도록 도와줌 티커는 $luna 시세가 유지되는 방식 테라와 루나는 서로 교환이 가능한데, 이 과정에서 바꾸는 코인은 소각되고 바꾸고자 하는 코인은 주조됩니다. 다시 말해 루나를 소각해서 테라를 발행할 수도 있고, 테라를 소각해서 루나를 발행할 수도 있습니다. 이 과정에서 테라의 가격은 법정화폐의 가치에 수렴됩니다.\n예를 들어 테라 가격이 급락하더라도 테라를 소각시켜 루나를 발행하여 시세차익을 얻으려는 사람들로 인해 일정하게 유지되고, 반대로 테라가격이 급등하면 루나를 소각해서 시세차익을 얻는 사람들로 인해 일정하게 유지될 것입니다.\n루나🌕 상승 이유에 대한 관점 저는 테라(UST)의 활용성 증가가 루나 소각을 이끌고 이것이 현재 루나의 가격 상승을 견인해오고 있다고 보고 있습니다. 최근에 이루어진 비공개 토큰 세일을 통해서 바닥이 증명되었다고 생각합니다.\n전세계인 위기로 스테이블 코인 수요 증가 최근 우크라이나-러시아 사태로 인해 국제정세는 불안감에 휩싸여 있습니다. 러시아가 우크라이나에 침공한 날 러시아 증시는 -40%에 가깝게 폭락했으며, 우크라이나에서는 정부의 현금 인출 제한 조치로 인해 현지 사람들은 스테이블 코인인 테더를 10% 프리미엄을 주고서 구매하고 있습니다.\n이러한 현상들은 모두 안전자산으로 대피하려는 움직임으로 해석할 수 있습니다. 전세계적으로 달러는 법정화폐 중에서도 가장 안전하다라고 인정받고 있기 때문에 국내에서까지 달러 환율이 상승하고 있죠.\n우리나라 또한 북한과 대립중인 나라임을 잊어서는 안됩니다. 분명 국내에 위기가 찾아왔을때 원화대비 달러 가치 상승이 올 수 있습니다. 따라서 자산 포트폴리오에 달러 또한 편입시켜야 한다고 생각합니다.\n이러한 가운데 달러와 연동되는 스테이블 코인은 지속적인 수요가 있을 것입니다. 법정통화 담보형 스테이블 코인의 경우 국가의 제재로부터 자유롭지 못한 반면, 테라(UST)는 탈중앙화되어 있는 시스템 상에서 발행되는 스테이블 코인이므로 국가에 통제될 수 없다는 점에서 매력적입니다.\n하락장 속에서 앵커 프로토콜 예치 증가 앵커 프로토콜은 테라의 예치(Saving) 디파이 서비스로, 달러와 대응되는 UST를 예치하는 것만으로도 연 19.5%에 달하는 이자수익을 낼 수 있습니다.\n앵커가 이자를 사용자들에게 제공할 능력이 없다는 문제로 한때 루나의 가격이 하락해왔습니다. 하지만 최근 테라 개발사인 TFL(Terraform Labs)에서 직접 해결하면서 당분간 이자가 지급되지 않을 일은 없어졌습니다. 하락장 속에서 꾸준히 고수익을 낼 수 있는 거의 유일한 피신처가 되었다고 말해도 과언이 아니죠.\n실제로 한달동안 앵커의 Value 변화는 46%에 육박할 정도로 많은 돈이 유입되고 있습니다.\n체인별 TVL을 보면 이더리움의 TVL이 50%대로 하락하는 동안 테라의 TVL은 10%까지 올랐습니다. 이를 통해서 같은 기간동안 이더리움의 자금이 빠지고 테라에는 유입되고 있음을 알 수 있습니다.\nTVL(Total Value Locked): 디파이 생태계에 유입된 총 자금\n앵커 프로토콜을 비롯한 테라 디파이 활용의 증가 또한 테라 사용의 증가로 이어져 루나 소각을 일으키고 있으며, 이 또한 루나가 상승하는 결과로 이어지고 있다고 보고 있습니다.\n생태계 확장 코스모스 네트워크 합류 테라는 최근 콜롬버스-5 메인넷 업데이트 이후로 생태계 확장이 급격하게 이루어지고 있습니다. 이번 업데이트를 통해 코스모스 네트워크에 편입되면서, 코스모스 SDK를 활용한 다른 프로젝트와 연결되었습니다. 최근에는 네이티브 코인 스왑 서비스 토르체인($Rune) 또한 테라와의 연결을 예고했습니다. 이로서 테라(UST)가 코스모스 생태계 내에서 기축 통화로 활용될 가능성이 높아지고 있는 모습입니다.\n다양한 서비스 런칭 이전까지 테라의 dApp이라고는 앵커와 미러 프로토콜이 다였다고 해도 과언이 아닙니다. 이 두 서비스를 통해 알고리드믹 스테이블 코인 기반 디파이 생태계를 튼튼히 구축해 왔습니다.\n이제는 레이어1 블록체인으로서 할 수 있는 다양한 서비스(NFT, 메타버스, P2E 등)로의 영역 확장이 이루어질 차례입니다. 이미 개발 중이거나 런칭 중인 프로젝트들이 줄지어 서있습니다.\n테라의 프로젝트들에 대해 궁금하신 분은 코인100년장투님의 블로그를 참고하면 좋을 거 같습니다.\n특히 최근 테라 생태계에서의 메타버스 서비스 테라월드는 랜드세일을 진행중에 있으며, 컴투스의 경우 P2E를 위한 자체토큰 C2X를 테라에서 발행하겠다고 밝힌 바 있습니다. C2X의 경우 기대되는 것은 TFL가 직접 백커로 있다는 점입니다.\nLFG의 비공개 토큰 판매 LFG(Luna Foundation Gaurd)는 최근 비공개 토큰 판매를 통해 $1B(10억 달러)에 해당하는 투자금을 유치하였습니다. 해당 자금은 테라 생태계와 상관 관계가 낮은 자산(비트코인)을 통해 UST의 페깅 메커니즘을 강화하는 외환 보유고(Forex Reserve)로 사용될 것이라 밝혔습니다.\nLFG(Luna Foundation Gaurd): 테라 생태계 성장과 발전 지원금을 할당하는 비영리 조직\n$1B라는 금액은 원화로 1조 이상의 금액으로 충분히 대중들에게 충격을 주기에 충분한 토큰 세일이었습니다. 기관투자자들의 매수 평단은 약 $51이고 4년간 락업이 되어 판매할 수 없다는 사실을 통해, 50달러 부근에서 바닥이 나왔다고 짐작할 수 있습니다.\n총정리 테라는 스테이블 코인을 기반으로 하는 레이어1 블록체인이다. 테라의 활용이 늘어나면 루나가 소각되며 자연스래 가격 상승으로 이어진다. 테라는 아래와 같은 이유로 활용이 늘고 있다. 불안한 국제정세 속에서 스테이블 코인의 수요 증가 하락장 속에서의 앵커 프로토콜 예치 증가 콜롬버스-5 업데이트 이후 루나 생태계 확장 LFG(Luna Foundation Gaurd)의 비공개 토큰 세일로 이미 바닥이 증명되었다 루나 떡상 가즈아 ","permalink":"https://vanslog.io/ko/posts/blockchain/my-views-on-the-recent-luna-rise/","summary":"$Luna to the moon 🌕","title":"[Crypto] 최근 루나 상승에 대한 내 관점"},{"content":"코딩테스트를 준비하면서 프로그래머스에서 제공하는 문제들을 다시 풀어보고 있다. 완주하지 못한 선수라는 문제를 풀던 중, 문득 리팩터링이라는 책에서 반복문을 파이프라인으로 바꿔라는 격언이 있었다는 사실이 떠올랐다. 기존에 반복문으로 쓰여진 코드들을 모두 파이프라인으로 변경해보기로 결정했다.\n파이프라인은 함수형 프로그래밍의 기법 중 하나인데, 데이터 처리를 여러 단계로 처리하는 구조를 말한다.\n리팩터링 반복문으로 짠 코드 class Solution { public String solution(String[] participant, String[] completion) { String answer = \u0026#34;\u0026#34;; HashMap\u0026lt;String, Integer\u0026gt; pm = new HashMap\u0026lt;\u0026gt;(); for (String element: participant){ pm.put(element, pm.getOrDefault(element, 0)+1); } for (String element: completion){ pm.put(element, pm.get(element)-1); } for (String key: pm.keySet()){ if(pm.get(key) != 0){ answer += key; } } return answer; } } 옛날에 같은 문제를 풀었을 당시에 해결했던 코드는 다음과 같다. for문이 세 번이나 반복되는 코드인데, 로직 코드가 코드 블럭으로 둘러싸여 있는 탓에 한 눈에 읽히지 않는다.\n파이프라인으로 짠 코드 class Solution { public String solution(String[] participant, String[] completion) { Map\u0026lt;String, Integer\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); Arrays.stream(participant) .forEach(s -\u0026gt; map.put(s, map.getOrDefault(s, 0)+1)); Arrays.stream(completion) .forEach(s -\u0026gt; map.put(s, map.get(s)-1)); Optional\u0026lt;String\u0026gt; answer = map.keySet().stream() .filter(k -\u0026gt; map.get(k)!=0) .findFirst(); return answer.get(); } } 이번에 문제를 풀 때는 조언에 따라 파이프라인을 적용하였다. 모든 반복문을 Stream으로 변경하였는데, 실제로 코드가 간결해졌고 읽기 쉬운 코드가 되었다.\n파이프라인의 장점은 데이터를 어떻게(how) 조작하는가가 아니라 어떤(what) 작업을 하는가에 포커싱을 하여 코드의 의도가 잘 드러난다는 데에 있다는 것을 이번 문제 풀이를 통해서 알 수 있었다.\n항상 좋은 건 아니다? 리팩터링이나 클린코드에서는 퍼포먼스 측면도 중요하지만, 결국 사람이 읽기 쉬운 코드가 더 좋은 코드라고 했다. 그렇게 생각하면 파이프라인으로 짜여진 코드는 장점밖에 없지 않는가? 동일한 작업을 가독성 좋은 코드로 짤 수 있다니. 하지만 내 생각과는 다르게 인터넷에는 부정적인 내용들이 있었다.\n한 글에서는 퍼포먼스 측면에서 반복문과 Stream의 극적인 차이가 있기 때문에 사용을 자제해야 한다고 했다. 실제로 나는 반복문으로 짜여진 코드와 파이프라인을 이용한 코드의 속도 차이가 얼마나 나는지가 궁금했고, 그래서 두 코드의 속도 차이를 비교해보았다.\n반복문 파이프라인 테스트 1 1.58ms 1.42ms 테스트 2 2.17ms 2.16ms 테스트 3 2.59ms 2.29ms 테스트 4 7.06ms 3.18ms 테스트 5 2.89ms 3.13ms 테스트 6 55.80ms 41.25ms 테스트 7 83.86ms 95.49ms 테스트 8 82.06ms 109.19ms 테스트 9 106.20ms 134.83ms 테스트 10 119.71ms 81.35ms 결과적으로 어떤 것을 사용하든 비슷한 실행시간이 나왔다. 아니 때로는 오히려 파이프라인을 사용할 때 더 좋은 퍼포먼스를 보여주기도 했다.\n결론 앞선 코드 실행 결과를 두고보면 그렇게 걱정하지 않아도 될 수준이다. 자바8이 나온 당시에는 함수형 프로그래밍의 기법들이 적용된 시점이었기에 최적화에 문제가 있었을 지도 모르겠다. 하지만 지금은 자바16까지 나온만큼 많은 최적화를 거쳤을 것이다.\n무엇보다 퍼포먼스만을 위해서 가독성을 포기하는 것은 유지보수 측면에서 더 큰 비용을 야기할 수 있다. 일반적인 경우에는 파이프라인을 통해 기능을 개발하되, 크리티컬한 성능 저하가 있는 경우에 반복문을 사용하면 해결될 일이다.\n","permalink":"https://vanslog.io/ko/posts/language/java/refactoring-loops-to-collection-pipelines/","summary":"자바8에서 등장한 스트림 언제사용하는 것이 좋은가?","title":"[Java] 반복문을 파이프라인으로 바꿔라"},{"content":"자료형 종류 자료형은 크게 기본 자료형과 참조 자료형으로 나누어진다. 이번 포스팅에서는 자바 프로그래밍에 있어서 기초가 되는 자료형에 한해서만 알아보기로 한다. 세부적인 내용은 추후 포스팅에서 다뤄보도록 하겠다.\n기본 자료형(Primitive Type) 기본형은 우리가 흔히 알고 사용하는 int, float, char, boolean과 같은 자료형이다. 자바는 정적 타이핑으로 작성되는 언어이므로, 기본 자료형에 대해서 잘 알고 있어야 한다. 사용 목적에 맞는 자료형을 적재적소에 배치하는 것은 효율성 좋은 코드를 짤 수 있게 한다.\n자료형 의미 메모리 사이즈 범위 기본값 byte 8-bit 정수 1 byte -2⁷ ~ 2⁷-1 0 short 16-bit 정수 2 bytes -2¹⁵ ~ 2¹⁵-1 0 int 32-bit 정수 4 bytes -2³¹ ~ 2³¹-1 0 long 64-bit 정수 8 bytes -2⁶³ ~ 2⁶³-1 0L float 32-bit 부동소수점(IEEE 754) 4 bytes -3.40E+38 ~ 3.40E+38 0.0f double 64-bit 부동소수점(IEEE 754) 8 bytes 1.79E+308 ~ 1.79E+308 0.0d char 16-bit 유니코드 문자 2 bytes 0 ~ 2¹⁶-1 \\u0000 boolean 논리형 1 bit 0 or 1 0 (false) C/C++과 달리 자바는 정수형에서 unsigned형이 없다. 엄연히 따지면 java 8 이후부터는 int, long형에 한에서는 Wrapper Class를 통해 몇가지 정적 메서드를 사용할 수 있도록 하고 있다.\n참조 자료형(Reference Type) 참조형은 기본 자료형을 기초로 하여 만들어진 자료형이다. 대표적으로 자바에서 제공하는 String, Array, Map, Set 등과 같은 클래스(Class)와 인터페이스(Interface), 열거형(Enum)이 여기에 해당한다. 추가적으로 필요에 따라 사용자가 참조형 타입을 정의할 수도 있다.\nObject 모든 Class와 Enum은 Object 클래스를 상속한다. 다시말해 Object는 모든 Class와 Enum의 일반화된 타입이라는 것이다. 여기서 주의할 점은 Interface는 Object를 상속하지 않는다는 사실이다. 이와 관련된 내용은 자바 api 문서 Tree에 잘 드러나 있다.\n자바 api 문서에는 제공하는 모든 Class, Interface, Enum 등의 상세 스펙을 정의하고 있다. 이 사이트는 자바를 깊이 이해하는데 많은 도움을 주므로 자주 방문하면 좋다. 다만 자바 버전에 따라 api구현이 상이하므로 주의해야 한다.\nString String은 기본 자료형이 아니라는 사실은 익히 잘 알려져 있다. String은 char의 배열로 구현된 참조 자료형이다.\nC/C++의 경우는 문자열을 사용하기 위해서 실제로 char형의 배열을 직접 사용한다.\n자바가 C/C++과 달리 String형을 따로 제공하는 이유는 문자열에 유용한 메서드를 제공하기 위해서이다. charAt, concat, equals, indexOf, split와 같은 메서드를 이용하면 문자열을 쉽게 조작할 수 있다.\nArray 배열 또한 마찬가지로 기본 자료형이 아니다. 이는 다음과 같은 간단한 코드를 통해서 테스트 해볼 수 있다.\nint[] array = new int[10]; System.out.println(array instanceof Object); // true 모든 클래스는 Object의 상속 클래스이므로 위 코드를 통해 배열이 기본형이 아님을 알 수 있다. 만약 배열이 기본형이었다면 위와 같은 코드의 실행 결과 false가 나와야 한다.\nWrapper 클래스 Wrapper Class는 기본 자료형을 감싼 클래스이다. 대표적으로 Byte, Short, Integer, Long, Float, Double, Character, Boolean이 있다. 이것을 사용하는 이유는 앞서 설명한 String을 사용하는 이유와도 동일하다. 기본 자료형을 클래스로 랩핑하면 얻을 수 있는 이점은 유용한 메서드를 제공할 수 있다는 점이다.\n하지만 이보다 더 중요한 이유는 제네릭(Generic)에 있다. 제네릭에 사용되는 매개변수 T는 Object 자료형만 받을 수 있다. 이것은 다시 말해 클래스로 정의된 객체만을 전달받는다는 것이다. 다만 코드를 짜다보면 제네릭을 기본 자료형에 적용해야 하는 경우가 있다. 이러한 경우에는 Wrapper Class을 이용하면 문제가 해소된다.\n제네릭을 통한 유연한 프로그래밍을 기본 자료형에도 적용할 수 있게 하기 위해 Wrapper Class를 제공한다고 생각하면 이해가 쉬울 것이다.\n차이점 앞서 기본 자료형과 참조 자료형이 무엇인지, 그리고 어떠한 차이가 있는지에 대해서 알아보았다. 이들의 차이에 대해서 간단히 알아보고 이번 포스팅을 마치려고 한다.\n참조 자료형은 기본 자료형과 달리 메서드를 가질 수 있다. 참조 자료형의 기본값은 null이다. 따라서 참조형 객체가 초기화되지 않으면 nullPointerException이 발생한다. 기본 자료형의 기본값은 위 표를 참고하자. 참고문헌 btechsmartclass - java data types oracle - java documentation ","permalink":"https://vanslog.io/ko/posts/language/java/primitive-type-and-reference-type/","summary":"자바의 기본 자료형과 참조 자료형에 대해서 알아보자!","title":"[Java] 기본 자료형과 참조 자료형"},{"content":"코테를 준비하자 최근 코딩테스트를 준비해야겠다고 결심했다. 전역을 한 이후 앞으로 취업 준비를 하게 되거나 대외활동을 지원하는데 있어서 필수적인 요소라고 생각하기 때문이다. 입대 이전에도 조금씩 공부하려고 했는데 조금씩 해이해지다가 결국에는 안하게 되었다. 이번에는 마음을 붙잡고 열심히 해보려고 한다.\n어떤 언어가 적합할까? C++ 굳이 배워야 하나 코딩테스트에 C++이 성능 측면에서 우월하다는 사실은 자명한 사실이다. 하지만 파이썬, 자바를 주로 사용하는 웹개발자로서 C++을 코딩테스트 언어로 사용하는 것은 적절하지 않다고 생각했다. 무엇때문에 굳이 사용하지 않는 언어를 추가적인 노력을 들여 배워야 할까? 아직까지는 C++을 배워야 하는 당위성을 느끼지 못했다.\n자바 vs 파이썬 결국 C++을 제쳐두고 자바와 파이썬 둘 사이에서 많은 고민을 했던 것 같다. 파이썬은 그리 효율성이 좋은 언어는 아니므로, 코드의 속도 측면에서는 자바가 우위를 가진다. 하지만 그에 못지 않게 파이썬은 강력한 내장 라이브러리를 통해 복잡한 알고리즘도 간단하게 짤 수 있다는 장점이 있었다.\n자바로 결정한 이유? 굳이 따지자면 자바를 더 공부하고 싶었다. 최근 들어 클린코드나 리팩터링과 같은 책을 읽으면서 자바가 객체지향을 잘 표현한 언어라고 느껴졌고, 다시 한 번 자바에 매료되었다. 그리고 나는 현재보다 고수준의 애플리케이션을 개발할 수 있는 서버 개발자로 거듭나고 싶다는 목표가 생겼다.\n이를 위해서는 파이썬보다 자바를 공부하는 것으로 얻을 수 있는 것들이 많다고 판단했다. 자바는 파이썬보다 다형성과 같은 객체지향의 특성을 잘 구현하고 있는 언어이다. 자바를 택하게 되면 파이썬으로 짤 때보다는 다소 코딩테스트에 제출할 코드의 양이 길어지기는 하겠지만, 그 만큼 배울 수 있는 것도 많을 것이라 판단하였다.\n이제 남은 건 실천뿐 코테를 하기로 결심했고, 언어 또한 결정하였다. 이제 실천하는 것만 남았다. 이 글을 작성하면서 문득 코테를 잘 준비하는 방법은 꾸준한 문제풀이라는 생각이 들었다.\n군대에서 시간이 날 때마다 운동을 하고 있는데, 최근들어 입대 전에는 못느꼈던 근성장을 경험하고 있다. 운동을 꾸준히 하기 이전에는 자극이 느껴지지도, 성장이 느껴지지도 않던 것이 지금은 몸의 변화가 조금씩 보이고 있다. 이러한 눈에 띄는 작은 변화는 나로 하여금 계속해서 운동을 더 할 수 있게 한다.\n코테도 이와 마찬가지라고 생각한다. 처음에 시작할 때는 스스로 성장하고 있음을 느끼기 어렵고, 정답을 맞추지 못해 좌절감을 느끼기도 할 것이다. 하지만 꾸준히 코딩테스트 문제를 풀다보면 노력의 결실을 맛볼 날이 분명 올 것이다.\n","permalink":"https://vanslog.io/ko/posts/general/its-time-to-prepare-codingtest/","summary":"코딩테스트를 준비하기로 결심한 이유와 언어 선택에 대한 고민","title":"이젠 코테를 준비할 때"},{"content":"리팩터링을 언제해야 하는가에 대해 명확하게 정립된 규칙은 없다. 하지만 리팩터링이 절실한 코드들의 특징은 존재한다. 켄트 백은 이를 냄새(악취)라고 정의한다.\n기이한 이름 코드는 단순하고 명료하게 작성되어야 한다. 특히 이름은 보기만 해도 어떤 작업을 하는 코드인지 명확히 알 수 있어야 한다. 이러한 노력은 곧 코드를 이해하기 쉽게 만든다.\n만약 마땅한 이름이 떠오르지 않는다면 설계에 근본적인 문제가 있을 수 있다. 이름을 잘 정리하다 보면 코드가 간결해지는 경우도 있다.\n중복 코드 똑같은 코드 구조가 반복된다면 하나로 통합하여 더 나은 프로그램을 만들 수 있다.\n리팩터링 설명 함수 추출하기 간단한 중복 코드의 경우 중복 코드에서 추출된 메서드를 호출 문장 슬라이드하기 완전히 똑같지 않고 비슷한 경우 비슷한 부분을 한 곳에 모아 함수 추출하기를 더 쉽게 적용 메서드 올리기 자식 클래스간 중복 코드는 부모로 옮김 긴 함수 리팩터링 설명 짧은 함수 함수는 길수록 이해하기 어려움 간접 호출의 효과, 코드를 이해하고, 공유하고, 선택하기 쉬워짐 좋은 이름 주석을 달아야 할 만한 부분은 함수로 만듦 함수 본문에는 주석으로 설명하려던 코드를 담음. 함수 이름은 동작 방식이 아닌 의도가 드러나게 지음. 긴 매개변수 목록 매개변수 또한 길어지면 이해하기 어려워 진다.\n리팩터링 설명 매개변수를 질의 함수로 바꾸기 다른 매개변수에서 값을 얻어올 수 있는 매개변수 제거 객체 통째로 넘기기 원본 데이터 구조로 전달 매개변수 객체 만들기 항상 함께 전달되는 매개변수 묶기 플래그 인수 제거 함수 동작방식을 제어하는 플래그 제거 여러 함수를 클래스로 묶기 공통 값들을 클래스의 필드로 정의 여러 개의 함수가 특정 매개변수들의 값을 공통으로 사용할 때 유용 전역 데이터 전역 데이터는 코드베이스 어디에서든 건드릴 수 있고 값을 누가 바꿨는지 찾아낼 메커니즘이 없다는 게 문제다.\n리팩터링 설명 변수 캡슐화하기 함수로 데이터를 감쌈 데이터 수정되는 부분을 쉽게 찾을 수 있음접근 통제가 가능 가변 데이터 데이터를 변경하면 예상치 못한 결과나 골치 아픈 버그로 이어지는 경우가 종종 있다.\n리팩터링 설명 변수 캡슐화하기 정해놓은 함수를 거쳐야만 값 변경 가능하게 설정 값이 어떻게 수정되는지 감시 가능 코드를 개선하기 용이 변수 쪼개기 용도가 다른 값은 용도 별로 독립 변수에 저장 갱신 로직은 다른 코드와 떨어뜨려 놓는 것이 좋음 문장 슬라이드하기와 함수 쪼개기 이용 무언가를 갱신하는 코드로부터 부작용이 없는 코드를 분리 질의 함수와 변경 함수 분리하기 부작용이 있는 코드를 호출하지 못하게 함 세터 제거하기 변수의 유효범위 줄이는 데 도움이 됨 파생 변수를 질의 함수로 바꾸기 값을 다른 곳에서 설정할 수 있는 가변 데이터는 악취가 심함 여러 함수를 클래스 / 변환 함수로 묶기 유효범위가 넓은 변수는 문제를 일으킬 위험이 큼 변수를 갱신하는 코드들의 유효범위를 제한 참조를 값으로 바꾸기 구조체처럼 내부 필드에 데이터를 담고 있는 변수의 경우 내부 필드를 직접 수정하지 말고 구조체를 통째로 교체하는 편이 나음 뒤엉킨 변경 소프트웨어의 구조는 변경하기 쉬운 형태로 조직되어야 한다. 그렇지 않다면 뒤엉킨 변경이나 산탄총 수술이 풍기기 마련이다.\n뒤엉킨 변경은 SRP(단일 책임 원칙, Single Responsibility Principle)이 만족하지 않을 때 발생한다. 즉, 하나의 모듈이 서로 다른 이유들로 인해 변경되는 일이 많을 때 발생한다.\n비즈니스 로직과 데이터베이스 연동은 서로 다른 맥락에서 이뤄지므로 독립된 모듈로 구성해야 마땅하다. 하지만 개발 초기에는 이러한 경계를 나누는 것이 쉽지 않으며, 개발 과정에서 이 경계 또한 끊임없이 변경된다.\n리팩터링 설명 단계 쪼개기 비즈니스 로직에서 데이터를 가져와서 처리하는 것과 같은 순차적인 맥락의 경우 다음 맥락에 필요한 데이터를 특정한 데이터 구조에 담아 전달하게 하는 식으로 단계를 분리 함수 옮기기 전체 처리 과정 곳곳에서 각기 다른 맥락의 함수를 호출하는 빈도가 높은 경우 각 맥락에 해당하는 적당한 모듈들을 만들어서 관련 함수들을 모음 함수 추출하기 함수 옮기기를 할 때 여러 맥락의 일을 관여하는 함수가 있는 경우 수행 클래스 추출하기 모듈이 클래스인 경우 클래스 추출하기를 통해 맥락별로 분리 산탄총 수술 산탄총 수술은 뒤엉킨 변경과 비슷하면서도 정반대다.\n이 냄새는 코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많을 때 풍긴다. 변경할 부분이 코드 전반에 퍼져 있다면 찾기도 어렵고 꼭 수정해야 할 곳을 지나치기 쉽다.\n리팩터링 설명 함수 옮기기 / 필드 옮기기 변경되는 대상들을 한 모듈로 묶음 여러 함수를 클래스로 묶기 비슷한 데이터를 다루는 함수가 많은 경우 여러 함수를 변환 함수로 묶기 데이터 구조를 변환하거나 보강하는 함수들의 경우 단계 쪼개기 이렇게 묶은 함수들의 출력 결과를 다음 단계의 로직으로 전달할 경우 함수 인라인하기 / 클래스 인라인하기 어설프게 분리된 로직으로 인한 산탄총 수술에 대처하는 좋은 방법 메서드나 클래스가 비대해지지만 나중에 추출하기 리팩터링으로 개선 기능 편애 어떤 함수가 자기가 속한 모듈의 함수나 데이터보다 다른 모듈과 상호작용할 일이 더 많을 때 풍기는 냄새\n리팩터링 설명 함수 옮기기 연관되는 함수와 데이터를 가까이 함 함수 추출하기 함수의 일부에서만 기능을 편애하는 경우 그 부분과 독립 함수로 빼냄 함수 옮기기로 원하는 모듈로 이동 어디로 옮길지가 명확하게 드러나지 않는다면 가장 많은 데이터를 포함한 모듈로 이동하자\n데이터 뭉치 데이터 항목들이 항상 함께 뭉쳐 다니는 데이터 뭉치는 보금자리를 따로 마련해줘야 마땅하다.\n리팩터링 설명 클래스 추출하기 필드 형태의 데이터 뭉치를 하나의 객체로 묶음 매개변수 객체 만들기 / 객체 통째로 넘기기 메서드 시그니처에 있는 데이터 뭉치의 경우 이 리팩터링을 적용하여 매개변수 수를 줄임 클래스를 이용하면 좋은 향기를 흩뿌릴 기회가 생긴다.\n기본형 집착 프로그래밍 언어에서 제공하는 기본형에 집착하고, 주어진 문제에 맞는 기초 타입을 직접 정의하기 꺼리는 사람이 많다.\n기본형을 객체로 바꾸기 기본 자료형을 감싸기만 한 것처럼 보임 필요한 경우 특별한 동작을 더할 수 있음 기본형으로 표현된 코드가 조건부 동작을 제어하는 타입 코드로 쓰이는 경우 타입 코드를 서브클래스로 바꾸기 조건부 로직을 다형성으로 바꾸기 위 리팩터링을 순차 적용한다 반복되는 switch문 조건부 로직을 다형성으로 바꾸기를 통해 가능한 한 switch문은 없애주는 편이 좋다. 조건절을 추가할 떄마다 다른 switch문들도 모두 찾아서 함께 수정해줘야 하기 떄문이다.\n반복분 지금은 일급 함수(first-class function)을 지원하는 언어가 많다. 반복문을 파이프라인으로 바꾸기를 적용하여 시대에 걸맞지 않은 반복문을 제거하자. 필터나 맵 같은 파이프라인 연산을 사용하면 코드에서 각 원소들이 어떻게 처리되는지 쉽게 파악 가능하다.\n성의 없는 요소 실질적으로 메서드가 하나뿐인 클래스와 같은 부실한 모듈의 경우 제거 작업을 진행한다. 함수 인라인하기나 클래스 인라인하기로 처리한다. 상속을 사용한 경우 계층 합치기를 사용한다.\n추측성 일반화 당장은 필요 없는 모든 종류의 후킹 포인트와 특이 케이스 처리 로직을 작성해둔 코드에서 풍긴다.\n하는 일이 거의 없는 추상 클래스 계층 합치기 쓸데없이 위임하는 코드 함수 인라인하기나 클래스 인라인하기로 삭제 본문에서 사용되지 않는 매개변수 함수 선언 바꾸기로 삭제 테스트 코드 말고는 사용하는 곳이 없는 함수나 클래스 죽은 코드 제거하기 임시 필드 특정 상황에서만 값이 설정되는 필드를 가진 클래스가 간혹 있다. 이러한 경우 사용자는 쓰이지 않는 것처럼 보이는 필드가 존재하는 이유를 파악하느라 머리를 싸매게 된다.\n덩그러니 떨어져 있는 필드를 발견하면 클래스 추출하기로 모은다. 임시 필드들과 관련된 코드를 함수 옮기기를 이용해 앞에서 만든 클래스에 넣는다. 임시 필드들이 유효한지 확인한 후 동작하는 조건부 로직은 특이 케이스 추가하기로 필드들이 유효하지 않을 때를 위한 대안 클래스를 만들어서 제거한다. 참고문헌 마틴 파울러,『리팩터링 2판』, 한빛미디어 ","permalink":"https://vanslog.io/ko/posts/cs/refactoring/smell-in-code/","summary":"코드에서 나는 악취, 코드 스멜","title":"[Refactoring] 코드에서 나는 악취"},{"content":"코드 서버를 개인 서버에서 운용하게 되면서 언제 어디서든 인터넷만 가능하면 코딩할 수 있고 블로그 포스팅도 가능한 환경을 구성할 수 있었다.\n다만 하나 아쉬웠던 거라고 하면 손쉽게 원하는 폰트를 사용할 수 없다는 점이었다. 로컬에서 VS Code로 작업할 때처럼 간단하게 설정해서 원하는 폰트를 사용할 수 있다면 편리하겠지만 그렇게 쉬운 일이 아니었다.\n만약에 코드 서버에 접속할 PC가 모두 내가 사용하는 폰트가 설치되어 있다면 정상적으로 출력이 될 수 있다. 하지만 이런 환경이 항상 보장되는 것은 아니다. 우리가 코드 서버를 사용하는 이유는 언제 어디서나 동일한 개발 환경을 제공받기 위해서이다. 하지만 매번 폰트를 설치해야 한다는 이러한 취지에 위반된다고 생각했다.\n해결 방법 이를 해결하는 방법으로는 코드 서버에 폰트도 같이 호스팅하는 것이다. 이것은 간단한 소스 코드 수정으로 가능하다.\n우선 /usr/lib/code-server/src/browser/pages 경로에서 vscode.html 파일에 아래와 같은 코드를 추가한다.\n\u0026lt;head\u0026gt; ... \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; type=\u0026#34;text/css\u0026#34; href=\u0026#34;{{CS_STATIC_BASE}}/src/browser/pages/fonts.css\u0026#34;\u0026gt; ... \u0026lt;/head\u0026gt; 이후 동일한 경로에 fonts.css 파일을 생성하고 아래와 비슷한 방식으로 폰트를 설정한다. 폰트는 /usr/lib/code-server/src/browser/media에 담아두었다.\n평소에도 D2Coding-ligature를 즐겨 사용했기에 해당 폰트를 사용할 수 있도록 설정하였다.\n@font-face { font-family: \u0026#39;D2Coding ligature\u0026#39;; src: url(\u0026#39;../media/D2Coding-ligature.woff2\u0026#39;) format(\u0026#39;woff2\u0026#39;), url(\u0026#39;../media/D2Coding-ligature.woff\u0026#39;) format(\u0026#39;woff\u0026#39;); font-weight: 400; font-style: normal; } 여기까지 완료했다면 VS Code에서 하던 것처럼 폰트를 설정해주고 코드 서버를 재시동해주면 끝이다.\nsudo systemctl restart code-server@$USER 문제점 해당 방법을 사용해서 폰트를 적용하면 코드 서버 업데이트 시 적용이 풀리는 현상이 있다. 해당 문제를 해결하는 방법을 찾아서 추후 소개하도록 하겠다.\n","permalink":"https://vanslog.io/ko/posts/infra/how-to-add-custom-fonts-on-code-server/","summary":"코드 서버에 커스텀 폰트 적용하기","title":"코드 서버에 커스텀 폰트 적용하기"},{"content":" 이 글은 마틴 파울러의 리팩터링 2판을 참고하여 쓰여졌습니다.\n마틴 파울러의 정의 리팩터링(refactoring)이라는 용어는 엔지니어 사이에서 두루뭉실하게 통용되고 있다. 리팩터링의 저자인 마틴 파울러는 리팩터링을 아래와 같이 정의한다.\n[명사] 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법 [동사] 소프트웨어의 겉보기 동작은 그대로 유지한 채, 여러 가지 리팩터링 기법을 적용하여 소프트웨어를 재구성하다. 여기서 겉보기 동작(observable behavior)라는 표현의 의미는 리팩터링의 전후 코드 동작이 동일하다는 것이다.\n많은 사람들이 코드를 정리하는 작업을 리팩터링이라고 표현하고 있는데, 앞에서 제시한 정의를 따르면 특정한 방식에 따라 코드를 정리하는 것만이 리팩터링이다.\n리팩터링은 수많은 작은 단계들이 순차적으로 변화를 만들어 내는데, 이 과정에서 코드는 항상 작동해야 한다. 만약 누군가 리팩터링하다가 코드가 깨져서 며칠이나 고생했다고 한다면, 이는 진정한 의미의 리팩터링이 아니다.\n두 개의 모자 소프트웨어를 개발할 때는 목적에 따라 기능 추가와 리팩터링을 명확히 구분해야 한다. 켄트 백은 이러한 방식을 두 개의 모자에 비유했다.\n켄트 백은 리팩터링의 선구자 중 한 명으로 리팩터링의 저자 마틴 파울러가 글을 집필하는 데 많은 도움을 준 이이다.\n기능을 추가할 때는 기능 추가 모자를 쓰고 새 기능을 추가한다. 리팩터링할 때는 리팩터링 모자를 쓰고 코드 재구성에만 전념한다. 기능 추가를 하다가 구조를 변경해야 작업하기 쉽겠다고 생각이 들면 잠시 모자를 바꿔쓰고 리팩터링을 한다. 코드 구조가 어느 정도 개선되었다면 다시 모자를 바꿔 쓰고 기능 추가를 이어간다.\n이러한 과정에서 내가 현재 쓰고 있는 모자가 무엇인지와 그에 따른 작업 방식의 차이를 분명히 인지하고 있어야 한다.\n리팩터링의 이점 좋은 소프트웨어 설계 아키텍처를 이해하지 않은 채 기능만 구현하다보면 기반 구조가 무너짐 결국 나중에 코드를 보았을 때 설계 파악에 어렵게 됨 리팩터링은 설계가 부패하는 것을 막음 이해하기 쉬운 소프트웨어 소스 코드는 컴퓨터만 읽는 것이 아님 리팩터링은 코드가 더 잘 읽히게 도와줌 코드의 목적이 더 잘 드러나게 의도를 더 명확하게 버그를 찾기 쉽다 코드를 이해하기 쉽다는 것은 버그를 찾기 쉽다는 것과 같은 말이다. 명확한 프로그램 구조는 버그를 지나칠래야 지나칠 수 없게 한다. 프로그래밍 속도를 가속화 리팩터링이 개발 속도를 더디게 한다고 오해하기 쉬움 좋은 설계는 새 기능 구축을 돕는 견고한 토대가 됨 따라서 빠른 개발에는 리팩터링이 반드시 필요 리팩터링을 해야하는 시점 준비를 위한 리팩터링 코드베이스에 기능을 새로 추가하기 직전 구조를 바꾸면 다른 작업이 훨씬 쉬워질 만한 부분을 찾는다 이해를 위한 리팩터링: 코드를 이해하기 쉽게 만들기 코드가 하는 일을 우선 파악 의도가 더 명확하게 드러나도록 리팩터링할 여지는 없는지 찾음 이해를 위한 리팩터링(Comprehension Refactoring) 변수는 적절한 이름으로 변경 긴 함수를 잘게 나눔 쓰레기 줍기 리팩터링 비효율적인 코드 복잡한 로직 매개변수화 함수면 될 일을 여러 함수로 작성 시간을 조금씩 들여서 개선 작업을 나누어서 진행 계획된 리팩터링과 수시로 하는 리팩터링 리팩터링은 프로그래밍과 구분되는 별개의 활동이 아님 이는 마치 if문 작성 시간을 따로 두지 않는 것과 같음 다른 일을 하는 도중에 처리 잘 작성된 코드 역시 리팩터링을 거쳐야 함 계획된 리팩터링이 나쁘다는 것은 아니나 최소한으로 줄임 기회가 될 때마다 진행 오래 걸리는 리팩터링 대규모 리팩터링 라이브러리 교체 일부 코드를 다른 팀과 공유하기 위해 컴포넌트화 의존성 정리 팀 전체가 위와 같은 문제에 매달리는 것은 좋지 않음 문제를 몇 주에 걸쳐 해결하는 편이 효과적 리팩터링할 코드와 관련된 작업을 하는 이가 조금씩 개선 라이브러리 변경시 추상 인터페이스를 마련 기존 코드가 이 추상 인터페이스를 호출하도록 만듦 이 전략을 추상화로 갈아타기(Branch By Abstraction)이라 함 코드 리뷰에 활용 코드 리뷰의 장점 개발팀 전체에 지식 전파 시니어 개발자의 노하우 전수 다양한 측면을 이해 클린 코드 작성 다른 사람의 아이디어 얻기 코드 리뷰의 결과를 더 구체적으로 도출 개선안들을 제시하는 데 그치지 않고 상당수를 직접 구현해볼 수 있음 더 좋은 아이디어를 생각해낼 수도 있음 리팩터링을 하지 말아야 할 때 굳이 수정할 필요가 없는 경우 처음부터 작성하는 게 오히려 나을 때 고려할 문제 새 기능 개발 속도 저하 많은 사람들이 리팩터링이 새 기능을 개발하는 속도를 느리게 한다고 여기지만, 리팩터링의 궁극적인 목적은 사실 개발 속도를 높이는 데 있다. 리팩터링이 개발 속도 지연을 가져온다는 여기는 사람이 여전히 많기 때문에 리팩터링은 실전에서 제대로 적용하지 못하고 있다.\n그렇지만 리팩터링에 대한 오류를 범해서는 안된다. 리팩터링을 클린 코드(Clean Code)나 바람직한 엔지니어링 습관처럼 도덕적인 이유로 정당화하는 것이다. 리팩터링의 본질은 코드베이스를 예쁘게 꾸미기 위한 것이 아니다. 오로지 경제적인 이유(개발 기간 단축)로 하는 것이다.\n코드 소유권 리팩터링하다 보면 모듈의 내부뿐 아니라 시스템의 다른 부분과 연동하는 방식에도 영향을 주는 경우가 많다. 가령 함수 이름을 바꾸려 할 때 호출하는 코드의 소유자가 다른 팀이라서 나에게는 쓰기 권한이 없을 수도 있다. 또는 API로 제공되는 함수라면 얼마나 사용되는 지는 고사하고 실제로 사용되는 지조차 모를 수도 있다.\n코드 소유권이 나누어져 있으면 리팩터링에 방해가 된다. 클라이언트에 영향을 주지 않고서는 원하는 형태로 변경할 수 없기 때문이다. 그렇다고 방법이 없는 것은 아니다. 함수 이름을 변경할 때 기존 함수도 그대로 유지하되 함수 본문에서 새 함수를 호출하도록 하는 것이다. 기존 함수는 폐기 대상으로 지정하고 시간이 흐른 뒤 삭제하도록 한다.\n가장 좋은 방법은 코드의 소유권을 팀에 두는 것이다. 그래서 팀원이라면 누구나 팀이 소유한 코드를 수정할 수 있게 한다. 이렇게 느슨하게 코드 소유권을 정하는 방식은 여러 팀으로 구성된 조직에도 적용된다. 팀별로 브랜치를 따서 커밋을 요청하는 오픈소스 개발 모델을 사용하면 된다. 이렇게 하면 함수의 클라이언트도 바꿀 수 있다.\n브랜치 일반적으로 팀 단위 작업 방식은 버전 관리 시스템을 사용하여 브랜치로 작업하고 통합하는 것이다. 이 방식을 활용하면 버전을 명확히 할 수 있으며 기능의 문제가 생기면 쉽게 롤백이 가능하다는 것이다. 이 방법의 단점이 있다면 독립 브랜치로 작업하는 기간이 길어질수록 작업 결과를 마스터로 통합하기 어려워진다는 점이다.\n브랜치 간 변경사항이 달라지다보면 충돌(Conflict)이 발생하기 쉽다.\n따라서 이러한 문제를 해결하기 위해서 수시로 리베이스(rebase)하거나 머지(merge)해야 한다. 브랜치의 통합 주기를 짧게 하는 것을 지속적 통합(CI, Continuous Integration) 또는 트렁크 기반 개발(Trunk-Based Development)이라고 한다. 리팩터링과 CI는 함께 사용할 때 빛을 발한다. 켄트 백은 리팩터링과 CI를 합쳐서 개발하는 방법으로 익스트림 프로그래밍(XP, Extreme Programming)을 고안했다.\n테스팅 리팩터링의 가장 큰 특징은 내부 구조는 변화하더라도 겉보기 동작은 동일하게 유지된다는 것이다. 만약에 실수를 저지른다 하더라도 정상적으로 작동하던 이전 버전으로 돌아가면 될 것이다.\n여기서 핵심은 오류를 재빨리 잡는 것인데, 이를 위해서는 테스트 스위트(Test Suite)가 필요하다. 이를 빠르게 실행할 수 있어야 수시로 테스트하는 데 부담이 없다. 즉, 리팩터링을 위해서는 자가 테스트 코드(self-testing code)가 필요하다.\n테스트 코드는 리팩터링을 도와줄 뿐 아니라 새로운 기능을 안전하게 추가할 수 있도록 한다. 실수로 만든 버그를 빠르게 찾아서 제거할 수 있기 때문이다. 이를 뒷받침하는 견고한 테스트를 마련하는 것은 무척이나 중요하다.\n자가 테스트 코드는 통합 과정에서 발생하는 의미 충돌을 잡는 메커니즘으로 활용할 수 있어서 자연스럽게 CI와도 밀접하게 연관된다. CI에 통합된 테스트는 XP의 권장사항이자 지속적 배포(CD, Continuous Delivery)의 핵심이기도 하다.\n레거시 코드 레거시 코드(Legacy Code)는 대체로 복잡하고 테스트도 제대로 갖춰지지 않은 것이 많다. 이러한 레거시 코드를 이해하는데 리팩터링을 활용하면 도움이 된다. 여기에 테스트를 추가하면 더 명료하게 리팩터링할 수 있다.\n테스트를 추가할 틈새를 찾아서 시스템의 모든 부분을 테스트하는 것이 가장 좋은 방법이겠지만, 방대한 레거시 코드를 모두 테스트하는 것은 힘든 일이다. 자주 보는 부분을 중점으로 더 많이 리팩터링하는 방법이 효과적이다. 코드를 훑게 되는 횟수가 많다는 것은 그 부분을 이해하기 쉽게 개선했을 때 얻는 효과도 그만큼 크다는 뜻이니 말이다.\n데이터베이스 과거에는 데이터베이스를 리팩터링하기 어려웠으나 진화형 데이터베이스 설계와 데이터베이스 리팩터링 기법의 등장으로 이는 틀린 말이 되었다. 이 기법의 핵심은 마이그레이션 스크립트를 작성하여, 접근 코드와 데이터베이스 스키마에 대한 구조적 변경을 이 스크립트로 처리하게끔 통합하는 데 있다.\nORM에서 사용하는 데이터베이스 마이그레이션이 대표적인 예이다.\n다른 리팩터링과 마찬가지로 이 기법도 전체 변경 과정을 작고 독립된 단계들로 쪼갠다. 그래야 마이그레이션 후에도 정상 작동할 수 있다.\n데이터베이스 리책터링은 프로덕션 환경에 따라 여러 단계로 나눠서 릴리스하는 것이 대체로 좋다는 점이 다른 리팩터링과 다르다. 이렇게 하면 프로덕션 환경에서 문제가 생겼을 때 변경을 되돌리기 쉽다.\n이를테면 필드 이름을 바꿀 때 첫 번쨰 커밋에서는 새로운 데이터베이스 필드를 추가만 하고 사용하지 않는다. 그런 다름 기존 필드와 새 필드를 동시에 업데이트하도록 설정한다. 그 다음에는 데이터베이스를 읽는 클라이언트들을 새 필드를 사용하는 버전으로 조금씩 교체한다. 이 과정에서 발생하는 버그도 해결하면서 클라이언트 교체 작업을 모두 끝냈다면, 더는 필요가 없어진 예전 필드를 삭제한다.\n이렇게 데이터베이스를 변경하는 방식은 병렬 수정(parallel change) 또는 팽창 수축(expand contract)의 일반적인 예다.\n애그니(YAGNI) 리팩터링은 수년간 운영되던 소프트웨어라도 아키텍처를 대폭 변경할 수 있게 한다. 리팩터링이 아키텍처에 미치는 실질적인 효과는 요구사항의 변화에 자연스럽게 대응하도록 코드베이스를 잘 설계해준다는 데 있다.\n요구사항 변경에 유연하게 대처하기 위한 또 다른 방법으로는 유연성 메커니즘(flexibility mechanism)이 있다. 가령 함수를 범용적으로 사용하기 위해 예상 시나리오에 대응하는 매개변수들을 추가한다. 이때 매개변수이 유연성 메커니즘이다. 하지만 모든 상황을 고려하다보면 오히려 변화에 대응하는 능력을 떨어뜨리는 경우가 발생한다. 요구사항이 개발도중 변경될 수도 있기 때문이다.\n리팩터링을 활용하면 다르게 접근 할 수 있다. 우선 현재까지 파악된 요구사항을 만족하는 소프트웨어를 우선 구축한다. 개발을 진행하면서 요구사항을 더 잘 이해하게 되면 아키텍처도 그에 맞게 리팩터링한다. 이런 식으로 설계하는 방식을 간결한 설계, 점진적 설계, YAGNI(you aren't going to need it)라고 한다.\n유연성 매커니즘은 요구사항을 예상해서 구현한다면 리팩터링은 파악된 요구사항에 계속해서 확장하는 방식이다.\nYAGNI는 아키텍처와 설계를 개발 프로세스에 녹이는 또 다른 방식이다. 이는 아키텍처에 소홀하라는 의미가 아니다. 미리 생각해서 시간이 절약될 수도 있지만 나중에 문제를 더 깊이 이해했을 때 처리하는 쪽이 훨씬 좋을 수 있다. 이러한 경향은 진화형 아키텍처 원칙이 발전하는 계기가 되었다.\n소프트웨어 개발 프로세스 익스트림 프로그래밍(XP, Extreme Programming) CI(지속적 통합) + 리팩터링 최초의 애자일 소프트웨어 방법론 테스트 주도 개발(TDD, Test Driven Development) 자가 테스트 코드 + 리팩터링 YAGNI 실천을 위한 3요소 자가 테스트 코드 지속적 통합 리팩터링 지속적 배포(CD, Continuous Delivery) 소프트웨어를 언제든 릴리즈할 수 있는 상태로 유지 리팩터링과 성능 직관적인 설계 vs 성능은 중요한 주제이다. 리팩터링하면 소프트웨어가 느려질 수도 있기는 하나 동시에 성능을 튜닝하기 더 쉬워진다.\n빠른 소프트웨어 작성법 시간 예산 분배 방식 설계를 여러 컴포넌트로 나눔 컴포넌트마다 자원(시간, 공간) 예산 할당 엄격한 시간 엄수 끊임없는 관심 직관적 실제 효과는 변변치 않음 코드 중 극히 일부에서 대부분의 시간을 소비 전체 코드 중 90%는 최적화 효과가 거의 없음 성능에 영향을 주는 부분만 최적화 성능을 신경쓰지 않고 코드 구조에만 신경 성능 최적화 단계가 되면 다음 절차를 따름 프로파일러로 시간과 공간을 많이 잡아먹는 지점 찾음 해당 지점을 성능 개선 리팩터링과 같이 작은 단계로 진행 각 단계마다 컴파일/테스트 리팩터링이 성능 최적화에 유리한 이유 성능 튜닝에 투입할 시간 벌기 기능 추가 시간이 절약됨 남는 시간에 성능 개선 성능을 세밀하게 분석 가능 프로파일러가 지적하는 코드 범위가 좁음 따라서 튜닝하기 쉬움 참고문헌 마틴 파울러,『리팩터링 2판』, 한빛미디어 ","permalink":"https://vanslog.io/ko/posts/cs/refactoring/what-is-refactoring/","summary":"코드의 재구성, 리팩터링","title":"[Refactoring] 리팩터링이란?"},{"content":"객체들의 상호작용을 나타내는 다이어그램 중 하나로 객체들 사이의 메시지 송신과 그들의 순서를 나타낸다.\n구성요소 객체 가장 윗부분에 표현 왼쪽에서 오른쪽으로 객체들을 나열 객체이름 : 클래스이름 형식으로 표기 생명선 객체 아래에 이어지는 점선 해당 객체가 존재함을 의미 활성 구간 생명선을 따라서 좁고 긴 사각형 객체가 연산을 실행하는 상태 실행 시간을 고려해서 적당히 설정 메시지 형식 [시퀸스 번호][가드]: 반환 값:=메시지 이름([인자 리스트]) 화살표로 표시 시작 부분: 송신 객체 끝 부분: 수신 객체 가드(Guard) 메시지가 송신되는 데 만족해야 하는 조건 유형 유형 의미 동기 메시지 메시지 실행이 종료될 때까지 다음 작업을 수행할 수 없음 비동기 메시지 메시지 실행이 끝나기를 기다리지 않고 다음 작업 수행 가능 반환 메시지 메시지가 종료되었음을 의미하며 반드시 표기해야 하는 것은 아님 자체 메시지 자신에게 보내는 메시지 스테레오 타입 \u0026laquo;create\u0026raquo; 객체를 생성하는 메시지 \u0026laquo;destroy\u0026raquo; 객체를 소멸시키는 메시지 생명선 끝에 X를 넣음 프레임 모든 다이어그램에 경계, 타입, 이름을 포함한 레이블의 장소를 제공(UML 2.0) 다이어그램을 에워싸는 박스로 표시 박스 안 왼쪽 모서리에 다이어그램 타입과 이름을 표시 sd: 순차 다이어그램 uc: 유스케이스 다이어그램 act: 액티비티 다이어그램 도서관에서 회원에게 도서를 대여하는 과정을 순차 다이어그램 프레임으로 표시한 것이다. 다만 위 다이어그램에는 대여에 성공한 경우만 나타내고 있다. 만약에 실패한 경우도 나타내고 싶다면 어떻게 해야 할까?\n이 경우에는 alt 키워드를 사용해서 상호작용을 조건에 따라 선택적으로 수행할 수 있게 하면 된다.\n회원의 비밀번호를 검증하는 과정을 몇 차례 반복하는 것을 표현하고 싶은 경우 loop 키워드를 사용하면 된다.\n단순히 다른 순차 다이어그램을 참조할 때는 ref 키워드를 사용한다.\n참고문헌 정인성, 채흥석,『JAVA 객체지향 디자인 패턴』, 한빛미디어 ","permalink":"https://vanslog.io/ko/posts/cs/uml/sequence-diagram/","summary":"객체들의 상호작용을 나타내는 다이어그램","title":"[UML 2.0] 순차 다이어그램(Sequence Diagram)"},{"content":" UML 2.0에서 디자인 패턴을 표현하는 도구 객체들이 특정 상황에서 수행하는 역할의 상호작용을 작성 UML 1.X에서 사용되던 컬레보레이션 다이어그램은 2.0에서 통신 다이어그램(Communication diagram)으로 변경되었다. 버전에 따라 다이어그램 이름이 상이하니 혼동하지 않도록 주의해야 한다.\n작성법 점선으로 된 타원 기호 사용 타원 내부에 협력을 필요로 하는 역할들과 그들 사이의 연결 관계를 표현 역할의 클래스를 명시하고자 할 때는 \u0026lt;role_name\u0026gt;:\u0026lt;class_name\u0026gt;으로 표현 컬레보레이션 담보 대출 관계를 보여주는 컬레보레이션 대출자, 대출인, 담보라는 역할이 필요 그들 사이의 협력이 요구되므로 이 역할들을 커넥터로 연결 컬레보레이션은 역할들의 상호작용을 추상화한 것 특별한 상황에 적용하면 많은 시스템 개발에 재사용 가능 컬레보레이션 어커런스 컬레보레이션에 대한 특별한 상황에 대한 적용\n컬레보레이션 어커런스(Collaboration Occurence)는 구체적인 상황에서의 컬레보레이션 적용을 표현해준다.\n담보 대출 컬레보레이션을 은행에서 집을 담보로 대출하는 경우에 적용 대출자 = 은행 담보 = 집 대출인 = 사람 이 경우 은행집담보대출은 담보대출의 한 예이며 컬레보레이션 어커런스이다. 참고문헌 정인성, 채흥석,『JAVA 객체지향 디자인 패턴』, 한빛미디어 ","permalink":"https://vanslog.io/ko/posts/cs/uml/collaboration-diagram/","summary":"UML2.0에서 디자인 패턴을 표현하는 도구","title":"[UML 2.0] 컬레보레이션 다이어그램(Collaboration Diagram)"},{"content":"좋은 객체지향 설계 객체 지향 소프트웨어를 설계하기란 쉬운 일이 아니다. 좋은 설계가 되기 위해서는 아래와 같은 요소가 필수적이다.\n유지보수성 기능확장 소프트웨어는 개발 단계에서도 요구사항의 변경이 있을 수 있으며, 서비스 중에도 끊임없이 기능 확장이나 유지보수가 이루어진다. 따라서 소프트웨어는 재설계를 하지 않아도 다시 사용할 수 있는 유연성을 제공해야 한다.\n역사는 반복된다. 프로그래밍을 하다보면 비슷한 문제를 반복적으로 마주하게 되고 이것을 해결하는 방법 또한 반복적으로 사용된다는 것을 인지하게 된다. 이미 경험한 문제를 다시 겪는다면 이전 경험을 통해 비교적 쉽게 해결할 수 있다. 이를 통해서 반복되는 설계 문제를 해결하는 데 경험이 중요하다는 것을 알 수 있다.\n그래서 전문가들은 경험을 통해 쌓아온 상황에 맞는 해결책을 디자인 패턴이라는 이름으로 정리했다. 이런 반복되는 패턴들은 특정 설계의 문제점을 해결해주고, 유연하며 재사용 가능한 객체지향 소프트웨어를 만들어 준다.\n디자인 패턴: 소프트웨어를 설계할 때 특정 맥락에서 자주 발생하는 고질적인 문제들이 또 발생했을 때 재사용할 수 있는 휼륭한 해결책\n건축가이자 디자인 패턴의 아버지, 크리스토퍼 알렉산더(1977/79)는 패턴에 대해 다음과 같이 설명한다.\n\u0026ldquo;Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this. solution a million times over, without ever doing it the same way twice.\u0026rdquo;\n바퀴를 다시 발명하지 마라 Don't reinvent the wheel은 디자인 패턴을 잘 설명하는 격언이다. 이미 만들어져서 잘 되고 있는 것을 처음부터 다시 만들 필요는 없다는 의미다. 즉, 불필요하게 처음부터 다시 시작하지 말라는 것을 강조하는 말이다.\n우리는 수십년에 걸쳐 검증된 디자인 패턴을 익히는 것으로 새로운 해결법을 찾는 노력을 들이지 않아도 된다.\nGoF의 디자인 패턴 디자인 패턴은 GoF(Gang of Four)가 Design Patterns: Elements of Reusable Object-Oriented Software에서 소개한 이후 엄청난 인기를 끌었다. 이들이 소개한 패턴은 총 23가지로 목적에 따라 생성 패턴, 구조 패턴, 행동 패턴으로 구분된다.\n목적에 따른 분류 생성 패턴 추상 팩토리(Abstract Factory) 구체적인 클래스를 지정하지 않고 객체들의 집합을 생성하는 인터페이스를 제공. 빌더(Builder) 복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 다른 결과를 만들 수 있게 함. 팩토리 메서드(Factory Method) 객체를 생성하는 인터페이스는 미리 정의하되 인스턴스를 만들 클래스는 서브클래스에서 결정. 프로토타입(Prototype) 생성할 객체의 종류를 명세하는 데 원형을 정의하고, 이를 복사함으로써 새로운 객체를 생성. 싱글턴(Singleton) 클래스의 인스턴스가 하나임을 보장하고, 이 객체에 접근할 수 있는 전역적인 접촉점을 제공. 구조 패턴 어댑터(Adapter) 클래스의 인터페이스를 사용자가 기대하는 것으로 변환하여 호환성을 해결. 브리지(Bridge) 구현부에서 추상층을 분리하여 각자 독립적으로 변형할 수 있게 함. 합성(Composite) 객체들의 관계를 트리 구조로 구성하여 부분-전체 계층을 표현. 데코레이터(Decorator) 주어진 상황 및 용도에 따라 어떤 객체에 책임을 덧붙이는 패턴. 파사드(Facade) 서브시스템에 있는 인터페이스 집합에 대해서 하나의 통합된 인터페이스를 제공. 플라이웨이트(Flyweight) 크기가 작은 객체가 여럿 있을 때, 공유를 통해 이들을 효율적으로 지원. 프록시(Proxy) 객체로 접근하는 것을 통제하기 위해서 대리자 또는 자리채움자를 제공. 행동 패턴 책임 연쇄(Chain of Responsiblity) 요청을 처리할 수 있는 기회를 하나 이상의 객체에게 부여하여 요청을 주고받는 객체 사이의 결합을 피함. 커맨드(Command) 요청을 객체의 형태로 캡슐화하여 서로 요청이 다른 사용자의 매개변수화, 요청 저장, 로깅, 연산 취소 지원. 해석자(Interpreter) 주어진 언어에 대해, 그 언어의 문법을 위한 표현 수단을 정의. 해당 문서를 해석하는 해석기 정의. 반복자(Iterator) 내부 표현부를 노출하지 않고 어떤 객체 집합에 속한 원소들을 순차 접근. 중재자(Mediator) 한 집합에 속해있는 객체들의 상호작용을 캡슐화하는 객체 정의. 메멘토(Memento) 캡슐화를 위배하지 않은 채로 어떤 객체의 내부 상태를 잡아내고 실체화시켜, 이후에 해당 객체가 그 상태로 되돌아올 수 있도록 함. 옵저버(Observer) 객체들 사이에 일대다 의존 관계를 정의하여, 어떤 객체의 상태가 변할 때 그 객체에 의존성을 가진 다른 객체들이 그 변화를 통지받고 자동으로 갱신할 수 있게 함. 상태(State) 객체 내부 상태에 따라 스스로 행동을 변경할 수 있게끔 허가. 전략(Strategy) 동일 계열 알고리즘군을 정의하고 각각의 알고리즘을 캡슐화하여 이들을 상호 교환이 가능하도록 만드는 패턴. 템플릿 메서드(Template Method) 객체의 연산에는 알고리즘의 뼈대만을 정의하고 각 단계에서 수행할 구체적 처리를 서브클래스 쪽으로 미룸. 방문자(Visitor) 객체 구조를 이루는 원소에 대해 수행할 연산을 표현하는 패턴. 범위에 따른 분류 클래스 패턴 클래스와 서브클래스 간의 관련성을 다루는 패턴 관련성은 주로 상속을 말함 컴파일 타임에 정적 객체 패턴 객체 관련성을 다룸 런타임에 동적 분류 생성 구조 행동 클래스 팩토리 메서드 적응자(class) 해석자\n템플릿 메서드 객체 추상 팩토리\n빌더\n원형\n단일체 적응자(object)\n가교\n복합체\n장식자\n퍼사드\n플라이급\n프록시 책임 연쇄\n명령\n해석자\n중재자\n메멘토\n감시자\n상태\n전략\n방문자 디자인 패턴 관계도 참고문헌 정인성, 채흥석,『JAVA 객체지향 디자인 패턴』, 한빛미디어 에릭 감마 외 3인, 『GoF의 디자인 패턴』, 프로텍미디어 UNC Department of Computer Science, GOF ","permalink":"https://vanslog.io/ko/posts/cs/design-patterns/what-is-the-design-patterns/","summary":"좋은 객체지향 설계를 위한 디자인 패턴","title":"디자인 패턴이란?"},{"content":"단일 책임 원칙(SRP) 단일 책임 원칙(Single Responsiblity Principle, SRP): 단 하나의 책임만 가져야 한다는 원칙\n책임의 의미 SRP에서 말하는 책임의 기본 단위는 객체를 지칭한다. 따라서 객체는 하나의 책임만 가져야 한다는 의미이다.\n그렇다면 책임이란 무엇인가? 보통 책임은 해야 하는 것이나 할 수 있는 것으로 간주한다. 그리고 객체에 책임을 할당할 때는 어떤 객체보다도 작업을 잘 할 수 있는 객체에 책임을 할당해야 한다. 또한 객체는 책임에 수반되는 모든 일을 자신만이 수행해야 한다.\n예를 들어 학생 클래스가 수강 과목을 추가하거나 조회하고, DB에 객체 정보를 저장하고 불러오는 작업을 처리하고, 성적표와 출석부에 출력하는 일까지 한다고 가정하자.\npublic class Student { public void getCourses() { ... } public void addCourse(Course c) { ... } public void save() { ... } public Student load() { ... } public void printOnReportCard() { ... } public void printOnAttendanceBook() { ... } } 이러한 경우 학생 클래스는 너무나 많은 책임을 수행해야만 한다. 단일 책임 원칙(SRP)을 만족하기 위해서는 학생 클래스가 가장 잘할 수 있는 책임(수강 과목 추가/조회)만을 남겨두는 것이다. DB작업이나 성적표/출석부 출력의 경우 다른 클래스가 잘할 수 있는 여지가 많다.\n변경 SRP를 따르는 실효성 있는 설계가 되려면 책임을 좀 더 현실적인 개념으로 파악할 필요가 있다. 우리가 설계 원칙을 학습하는 이유는 예측하지 못한 변경사항에 유연하고 확장성 있는 시스템 구조를 설계하기 위해서이다.\n좋은 설계란 기본적으로 시스템에 새로운 요구사항이나 변경이 있을 때 가능한 한 영향 받는 부분을 줄여야 한다. 가령 어떤 클래스가 잘 설계되었는지를 판단하려면 언제 변경되어야 하는지를 물어보는 것이 좋다.\n그렇다면 학생 클래스는 언제 변경되어야 하나?\nDB 스키마 변경 시? 학생이 지도 교수를 찾는 기능이 추가된다면? 새로운 형식으로 출력하고 싶다면? 이러한 사항은 모두 학생 클래스를 변경해야 하는 이유가 된다. 또한 책임을 많이 질수록 클래스 내부에서 서로 다른 역할을 수행하는 코드끼리 강하게 결합될 가능성이 높다.\n책임 분리 학생 클래스는 여러 책임을 수행하므로 이것의 도움을 필요로 하는 코드가 많을 수 밖에 없다. 그렇기 때문에 학생 클래스에 변경사항이 생기면 직접 또는 간접적으로 연관되어 있는 모든 코드들을 다시 테스트해야 한다.\n참고로 어떤 변화가 있을 때 기존 시스템 기능에 영향을 주는지 평가하는 테스트를 회귀 테스트라고 한다.\n모든 코드를 테스트하지 않기 위해서는 한 클래스에 너무 많은 책임을 부여하지 말고 단 하나의 책임만 수행하도록 해서 변경 사유가 될 수 있는 것을 하나로 만들어야 한다. 이것을 책임 분리라 한다.\n학생 클래스의 경우 변경 사유가 될 수 있는 것은 학생의 고유 정보, DB 스키마, 출력 형식의 변화 등 3가지이다. 따라서 학생 클래스는 고유의 역할만 수행하고 DB작업은 DAO(Data Access Object) 클래스, 출석부와 성적표에 출력을 담당하는 클래스로 분리하는 것이 좋다.\n산탄총 수술 지금까지는 한 클래스가 여러 가지 책임을 가진 상황을 살펴봤다. 반대로 하나의 책임이 여러 클래스들로 분산되어 있는 경우도 단일 책임 원칙에 입각해 설계를 변경해야 한다. 이러한 경우를 산탄총 수술(shotgun surgery)라고 한다.\n하나의 책임이 여러 개의 클래스로 분리되어 있는 예는 로깅, 보안, 트랜잭션과 같은 횡단 관심으로 분류할 수 있는 기능이 대표적이다. 횡단 관심에 속하는 기능은 대부분 시스템 핵심 기능 안에 포함되는 부가 기능이다.\n가령 시스템에서 실행하는 특정 메시지들의 실행 로그를 DB에 저장하는 코드가 있다고 할 때, 이것을 파일로 저장하게 변경한다면 로그 기능이 삽입된 메서드를 모두 찾아야만 한다.\n이를 해결하는 방법은 이러한 부가 기능을 별개의 클래스로 분리해 책임을 담당하게 하는 것이다. 즉, 여러 곳에 흩어진 공통 책임을 한 곳에 모으면서 응집도를 높인다. 그러나 여전히 구현된 기능들을 호출하고 사용하는 코드는 해당 기능을 사용하는 코드 어딘가에 포함될 수밖에 없다.\n횡단 관심 문제를 해결하기 위한 방법으로 관심지향 프로그래밍(AOP)라는 기법이 있다. AOP는 횡단 관심을 수행하는 코드를 애스펙트(aspect)라는 특별한 객체로 모듈화하고, 위빙(weaving)이라는 작업을 통해 모듈화한 코드를 핵심 기능에 끼워넣는다. 만약 횡단 관심에 변경이 생기면 기존 코드를 전혀 변경하지 않고 해당 애스팩트만 수정한다.\n개방-폐쇄 원칙(OCP) 개방-폐쇄 원칙(Open-Closed Principle, OCP): 기존의 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계가 되어야 한다는 원칙\n만약 도서관 대여 명부와 같은 새로운 매체에 학생의 대여 기록을 출력하는 기능을 추가하려면 어떻게 해야할까? 도서관 대여 명부 클래스를 새로 만들어서 Client 클래스가 이 기능을 이용하면 될 것처럼 보인다. 하지만 이러한 방식은 OCP를 위반한다. 새로운 기능을 추가함에 따라서 client를 수정해야만 하기 때문이다.\n새로운 기능을 추가하더라도 Client 클래스에 영향을 주지 않게 하려면 개별적인 클래스를 직접 접근하는 것이 아닌 인터페이스를 통해 구체적인 기능을 캡슐화하여 처리해야 한다.\n클래스는 변경하지 않고도(closed) 대상 클래스의 환경을 변경(open)할 수 있는 설계가 되어야 한다.\n리스코프 치환 원칙(LSP) 리스코프 치환 원칙(Liskov Substitution Principle, LSP): 자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행할 수 있어야 한다는 원칙\n일반화 관계는 다른 말로 is-a-kind-of 관계라고 한다. 예를 들어 원숭이와 포유류 사이에는 해당 관계가 성립한다(원숭이 is a kind of 포유류). 이때 부모 클래스로 포유류, 자식 클래스로 원숭이를 설정할 수 있다.\n- 포유류는 새끼를 낳아 번식한다. - 포유류는 젖을 먹여서 새끼를 키우고 폐를 통해 호흡한다. - 포유류는 체온이 일정한 정온 동물이다. 위는 포유류의 여러 특징을 설명한 것이다.\n- 원숭이는 새끼를 낳아 번식한다. - 원숭이는 젖을 먹여서 새끼를 키우고 폐를 통해 호흡한다. - 원숭이는 체온이 일정한 정온 동물이다. 리스코프 치환 원칙에 의해 자식 클래스는 부모 클래스에서 가능한 행위는 수행할 수 있다. 따라서 포유류의 특성은 원숭이도 마찬가지로 수행할 수 있다.\n오리너구리는 포유류일까? - 오리너구리는 새끼를 낳아 번식한다. - 오리너구리는 젖을 먹여서 새끼를 키우고 폐를 통해 호흡한다. - 오리너구리는 체온이 일정한 정온 동물이다. 이와 같은 상황을 오리너구리에 적용해보자. 오리너구리는 포유류임에도 새끼를 낳지 않고 알을 낳는 동물이다. 이러한 경우는 부모 클래스에서 가능한 행위를 수행하지 못한다. 따라서 위 포유류에 대한 설명이 잘못되었다고 결론을 내릴 수 있다.\nLSP를 만족하려면 부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 대신할 수 있어야 한다.\n실제로 포유류를 분류하는 데에는 태생을 기준으로 두지 않는다. 참고로 알을 낳는 포유류를 단공류라 한다. 따라서 단공류를 제외한 포유류는 태생이다라는 명제는 참이다.\n오버라이딩(override) 하지만 이러한 의문을 가질 수도 있을 것이다. 포유류 중에 알을 낳는 경우는 소수이므로 예외적인 경우만 재정의(override)해서 사용하면 안되는 걸까?\n- 오리너구리는 알을 낳아 번식한다. (override) - 오리너구리는 젖을 먹여서 새끼를 키우고 폐를 통해 호흡한다. - 오리너구리는 체온이 일정한 정온 동물이다. 만약 재정의를 사용하게 된다면 오리너구리는 위와 같이 정의할 수 있을 것이며, 실제로 코드는 잘 동작할 수 있다. 다만 아래와 같은 두 가지 OOP 규칙 위배가 발생한다.\nLSP를 만족하지 않음 오리너구리 클래스의 구현은 포유류 클래스의 행위와 일관되지 않음 피터 코드의 상속 규칙 위반 서브 클래스가 슈퍼 클래스의 책임을 무시하거나 재정의하지 않고 확장만 수행한다라는 규칙 피터 코드의 상속 규칙에서 말하는 재정의하지 않고 확장만 한다는 말은 결국 오버라이드하지 않는다는 것과 같은 의미이다. 따라서, 피터 코드의 상속 규칙을 따르는 것은 LSP를 만족시키는 방법 중 하나이다. Do Not Override!\n의존 역전 원칙(DIP) 의존 역전 원칙(Dependency Inversion Principle, DIP): 의존 관계를 맺을 때 변화가 어렵거나, 거의 변화가 없는 것에 의존하라는 원칙\n사람이 음료를 마시는 경우를 가정해보자. 우리는 날마다 물을 먹기도 하지만 커피를 마시거나 콜라를 즐기기도 한다. 구체적으로 무엇을 마시는가는 변하기 쉬운 것이지만 무언가를 마신다는 사실 자체는 변하기 어렵다.\n객체지향에서는 이와 같이 변화하기 어려운 추상적인 것을 표현하기 위해 추상 클래스나 인터페이스를 사용한다. DIP를 만족하기 위해서는 구체 클래스보다는 인터페이스나 추상 클래스와 의존 관계를 맺도록 설계해야 한다.\n의존성 주입(DI) 의존성 주입(Dependency Injection, DI)란 클래스 외부에서 의존되는 것을 대상 객체의 인스턴스 변수에 주입하는 기술이다. 이것을 이용하면 대상 객체를 변경하지 않고도 외부에서 대상 객체의 외부 의존 객체를 바꿀 수 있다.\npublic class Person { private Beverage beverage public void Person() { this.beverage = new Water(); // this.beverage = new Coffee(); // this.beverage = new Coke(); } public void drink(){ beverage.drink(); } } 우선 의존성 주입을 사용하지 않는 경우부터 살펴보겠다. 마시고 싶은 음료에 따라서 사람은 생성자에서 생성되는 음료를 선택할 수 있다. 하지만 위 같은 설계의 경우 마시는 음료가 변화함에 따라 생성자 코드를 변경해야만 한다.\npublic class Person { private Beverage beverage public void setBeverage(Beverage beverage) { this.beverage = beverage; } public void drink(){ beverage.drink(); } } 의존성 주입을 이용하는 경우 필요한 객체를 클래스 내부에서 직접 생성하는 것이 아니라 외부에서 주입하여 객체 간의 결합도를 줄이고 코드를 유연하게 할 수 있다. 의존성 주입은 외부에서 필요한 객체를 받아서 사용하는 것이다.\n인터페이스 분리 원칙(ISP) 인터페이스 분리 원칙(Interface Segregation Principle, ISP): 클라이언트 자신이 이용하지 않는 기능에는 영향을 받지 않아야 한다는 원칙\n복합기의 경우를 생각해보자. 복합기는 프린트 기능뿐 아니라 다양한 기능을 복합적으로 사용가능한 장치이다. 그렇기 때문에 여러 클라이언트의 작업 요청을 처리할 수 있어야만 한다.\n프린트 기능을 사용중인 클라이언트는 팩스나 복사 기능의 영향을 받아서는 안된다. 하지만 위와 같은 구조는 한 클래스 내에서 모든 기능을 구현하므로 관련 없는 기능에도 영향을 줄 가능성이 높다. 따라서 인터페이스를 통해 클라이언트에 특화되로록 분리시켜야 한다.\n복합기를 사용하는 객체들마다 자신이 관심을 갖는 메서드들만 있는 인터페이스를 제공받도록 설계했다. 이렇게 설계하면 인터페이스가 일종의 방화벽 역할을 수행해 클라이언트는 자신이 사용하지 않는 메서드에 생긴 변화로 인한 영향을 받지 않게 된다.\nSRP와 ISP 어떤 클래스가 단일 책임을 수행하지 않고 여러 책임을 수행하게 되면 방대한 메서드를 가진 비대한 클래스가 될 것이다. 이러한 클래스를 SRP에 따라 단일 책임을 갖는 여러 클래스들로 분할하고 각자의 인터페이스를 제공한다면 ISP도 만족할 수 있다.\n그렇다면 SRP는 ISP 만족을 위한 필요조건인가? 그렇다고만 할 수는 없다. 가령 게시판의 기능을 제공하기 위한 클래스가 있다고 있다고 하자. 이 클래스가 CRUD 메서드를 구현하고 있다면, 게시판에 관련된 책임을 수행하는 것이므로 SRP를 만족한다고 볼 수 있다.\n그러나 클라이언트에 따라서 게시판의 일부 기능만 사용하도록 제한될 수 있다. 글을 삭제하는 권한이 관리자에게만 있는 경우와 같이 말이다. 이 클래스의 모든 메서드가 들어 있는 인터페이스가 클라이언트와 상관없이 사용된다면 ISP에 위배된다.\n참고문헌 정인성, 채흥석,『JAVA 객체지향 디자인 패턴』, 한빛미디어 위키피디아, 포유류 wlsdud2194, [DI] Dependency Injection이란 무엇일까?, velog ","permalink":"https://vanslog.io/ko/posts/cs/oop/solid-principle/","summary":"객제지향 설계 원칙","title":"[OOP] 객체지향 설계 원칙: SOLID 원칙"},{"content":"추상화(Abstraction) 구체적인 사물들의 공통적인 특징을 파악해서 하나의 개념으로 다루는 수단이다. 즉 복잡한 자료, 모듈, 시스템 등으로부터 핵심적인 개념 또는 기능을 간추려내는 것이다.\n이러한 추상화는 객체지향 프로그래밍(OOP)에서 클래스를 만들기 위해 필수적인 요소이다. 만약 추상화가 없다면 모든 객체를 세부적으로 정의하여 구현해야만 한다.\n캡슐화(Encapsulation) 캡슐화는 정보은닉을 통해 높은 응집도와 낮은 결합도를 갖도록 하는 객체지향 설계 원리이다.\n응집도와 결합도 클래스의 디자인은 재사용 가능하고, 확장 가능하며 유지 보수가 용이해야 한다. 이를 위해서는 결합도는 낮아야 하며, 응집도은 높아야 한다.\n응집도(Cohesion) 클래스나 모듈 안의 요소들이 밀접하게 관련되어 있는 정도 결합도(Coupling) 어떤 기능을 실행하는 데 다른 클래스나 모듈들에 의존적인 정도 정보 은닉(information hiding) 알 필요 없는 정보는 외부에서 접근하지 못하도록 제한하는 것이다. 우리는 자동차가 어떻게 동작하는 지 몰라도 운전을 할 수 있으며, 컴퓨터가 동작하는 원리를 몰라도 잘 이용할 수 있다.\npublic class ArrayStack { public int top; public int[] itemArray; public int stackSize; public ArrayStack(int stackSize) { itemArray = new int[stackSize]; top = -1; this.stackSize = stackSize; } public void push(int item) { // item 추가 ... } public int pop() { // item 반환 ... } public int peak() { // item 출력 ... } ... } ArrayStack은 배열을 사용해서 구현된 스택이다. push 함수를 이용해서 아이템을 추가하고 pop을 통해서 반환할 수 있다.\npublic class StackClient { public static void main(String[] args){ ArrayStack st = new ArrayStack(10); st.itemArray[++st.top] = 20; System.out.print(st.itemArray[st.top]); } } 하지만 ArrayStack의 속성은 모두 public으로 지정되어 있어, 외부에서도 접근이 가능하다. 이러한 경우 push함수를 이용하지 않고 직접 배열을 다룰 수 있게 되며, StackClient와 ArrayStack 사이에는 강한 결합이 발생한다.\n만약 스택의 구현이 변경되는 경우 StackClient의 소스코드도 변경해야 할 수 있다. 그렇기 때문에 직접 스택의 속성을 다루는 것은 피해야 한다. 따라서 ArrayStack의 속성들은 접근 제어자를 private으로 설정해서 외부 클래스가 접근할 수 없도록 막는다. 이제 더 이상 직접 배열을 조작할 수 없게 되었으므로 StackClient는 아래와 같이 수정되어야 한다.\npublic class StackClient { public static void main(String[] args){ ArrayStack st = new ArrayStack(10); st.push(20); System.out.print(st.peek()); } } 일반화(Generalization) 여러 개체들이 가진 공통된 특성을 부각시켜 하나의 개념이나 법칙으로 성립시키는 과정이다.\n일반화 관계는 객체지향 프로그래밍 관점에서 상속 관계라고 한다. 따라서 속성이나 기능의 재사용만 강조해서 사용하는 경우가 많다. 이는 일반화 관계를 극히 한정되게 바라보는 시각이다. 캡슐화를 위한 일반화 일반화는 외부 세계에 자식 클래스를 캡슐화(또는 은닉)하는 개념으로 볼 수도 있다.\n예를 들어 우리는 자동차를 구분할 때 BMW, 현대, 벤츠와 같은 여러 제조사들로 구분한다. 대리 운전 기사가 운전을 하는 상황을 가정해보면 차종은 운전에 큰 영향을 주지 않는다. 사람 클래스 관점에서는 구체적인 자동차가 아닌 자동차 클래스만 관심을 가지면 된다. 따라서 구체적인 자동차 클래스는 은닉되어 있다고 볼 수 있다.\n집합론 관점에서 일반화 일반화 관계는 집합론적인 관점에서 해석할 수도 있다.\n부모 클래스 A는 전체 집합 A에 해당하고 그 부분 집합 A1, A2, A3는 각각 A의 자식 클래스에 해당한다. 이때 다음 관계가 성립되어야 한다.\nA = A1 ∪ A2 ∪ A3 A1 ∩ A2 ∩ A3 = ø 그리고 아래와 같은 제약 조건도 존재한다.\n{disjoint}: 자식 클래스 객체가 동시에 두 클래스에 속할 수 없다 {complate}: 자식 클래스의 객체에 해당하는 부모 클래스의 객체와 부모 클래스의 객체에 해당하는 자식 클래스의 객체가 하나만 존재 집합론 관점에서 일반화 관계를 만들면 연관 관계를 단순하게 할 수 있다.\n만약 인터넷 쇼핑몰에서 VIP 고객과 일반 고객을 분류하고 있다고 한다면 위와 같은 다이어그램을 그릴 수 있을 것이다. 각 회원은 각각 물건과 연관 관계를 맺게 할 수 있지만, 물건을 구매하는 것은 등급과는 무관하다.\n다시 말해 물건 클래스와의 연관 관계는 물건 클래스의 자식 클래스가 가지는 공통적인 연관 관계이다. 따라서 물건 클래스를 회원 클래스와 연관을 갖게 하여 다이어그램을 간결화할 수 있다,\n집합론적인 관점에서의 일반화는 상호 배타적인 부분 집합으로 나누는 과정으로 간주할 수 있다.\n만약 회원을 구분하는 기준이 추가된다면 어떻게 해야 할까? 웹 쇼핑몰에서는 국내 회원만 아니라 외국의 회원에게도 서비스를 제공하는 경우가 있다. 회원의 국가에 따라서 다른 서비스를 제공한다고 할 때, 이러한 구분은 꼭 필요할 것이다.\nUML에서는 이러한 구분을 변별자라 하며 일반화 관계를 표시하는 선 옆에 변별자 정보를 표시한다. 하지만 이러한 경우 회원은 구 가지 기준(결재금액, 지역)에 따라 구분되게 되는데, 이와 같이 한 인스턴스가 동시에 여러 클래스에 속할 수 있는 것을 다중 분류라고 한다. \u0026lt;\u0026lt;다중\u0026gt;\u0026gt; 스테레오 타입을 사용해서 표현한다.\n일반적으로 각 변별자에 따른 일반화 관계가 완전히 독립적이라면 별다른 문제가 없다. 하지만 요구사항의 변경/추가로 인해 두 일반화 관계가 독립적이지 않은 상황도 고려해야 한다.\n예를 들어 위 다이어그램에서 VIP 회원에게 할인 쿠폰을 제공한다는 것은 쉽게 구현이 가능하지만, 일반 등급인 외국 회원에게 선물을 제공한다는 것은 불가능하다.\n이를 처리하기 한 가지 방법은 모든 분류 가능한 조합에 대응하는 클래스를 만드는 것이다.\n다형성(Polymorphism) 서로 다른 클래스의 객체가 같은 메시지를 받았을 때 각자의 방식으로 동작하는 능력이다.\n앞서 캡슐화를 위한 일반화에서 보았던 자동차 사례를 생각해보자. 우리는 차의 브랜드와는 무관하게 운전하는 것이 가능하다. 그래서 사람 클래스의 관점에서는 자동차 클래스만을 관심을 가지면 된다고 했다. 이것이 가능한 이유는 다형성이 있기 때문이다.\nabstract class Car { public abstract void ride(); } public class BMW extends Car { public void ride() { ... } } public class HYUNDAI extends Car { public void ride() { ... } } public class BENZ extends Car { public void ride() { ... } } public class Main { public static void rideCars(Car[] cars) { for(Car car: cars){ car.ride(); } } public static void main(String[] args){ Car[] cars = {new BMW(), new HYUNDAI(), new BENZ()}; rideCars(cars); } } 다형성을 사용하는 경우 구체적으로 현재 어떤 클래스 객체가 참조되는지와 무관하게 프로그래밍할 수 있다. 따라서 새로운 자동차 클래스가 자식 클래스로 추가되더라도 코드는 영향을 받지 않는다.\n이것이 가능한 이유는 일반화 관계에 있을 때 부모 클래스의 참조 변수가 자식 클래스의 객체를 참조할 수 있기 때문이다. 단, 부모 클래스의 참조 변수가 접근할 수 있는 것은 부모 클래스가 물려준 변수와 메서드뿐이다.\n피터 코드의 상속 규칙 피터 코드는 상속의 오용을 막기 위해 상속의 사용을 엄격하게 제한하는 규칙들을 만들었다.\n자식 클래스와 부모 클래스 사이는 역할 수행 관계가 아니어야 한다. 한 클래스의 인스턴스는 다른 서브 클래스의 객체로 변환할 필요가 절대 없어야 한다. 자식 클래스가 부모 클래스의 책임을 무시하거나 재정의하지 않고 확장(extends)만 수행해야 한다. 자식 클래스가 단지 일부 기능을 재사용할 목적으로 유틸리티 역할을 수행하는 클래스를 상속하지 않아야 한다. 자식 클래스가 역할, 트랜잭션, 디바이스 등을 특수화해야 한다. 참고문헌 정인성, 채흥석,『JAVA 객체지향 디자인 패턴』, 한빛미디어 ","permalink":"https://vanslog.io/ko/posts/cs/oop/oop-features/","summary":"객체지향 프로그래밍(OOP)의 네가지 특징","title":"[OOP] 객체지향 프로그래밍(OOP) 특징"},{"content":"클래스란? 클래스는 동일한 속성과 행위를 수행하는 객체들의 집합이다. 예를 들어 소프트웨어를 전공중인 학생들의 공통점은 소프트웨어를 전공한다는 사실과 동일한 전공 수업을 듣는다는 점이다. 이러한 경우 소프트웨어 전공 중인 학생은 실제 학생들의 클래스라고 말할 수 있다.\n클래스를 정의하는 또 다른 관점은 인스턴스(객체)를 생성하는 설계도로 보는 것이다. 아래 소스코드는 소프트웨어를 전공하는 학생 클래스를 정의한 것이다. 클래스가 객체를 생성하는 설계도라는 관점에서 코드를 한 번 살펴보자\npublic class Student{ private String name; private String major = \u0026#34;SW\u0026#34;; public Student(String name){ this.name = name; } public void study() { System.out.println(\u0026#34;객체 지향 모델링 수업을 수강합니다.\u0026#34;); } } Student student1 = new Student(\u0026#34;학생1\u0026#34;); Student student2 = new Student(\u0026#34;학생2\u0026#34;); student1.study(); student2.study(); 위 코드를 통해서 같은 스펙을 가진 학생 객체가 두 개 생겨났다. 두 객체는 이름을 제외하면 모두 동일한 특성을 가진다. major 속성은 SW로 동일하며 study() 메소드를 실행하면 동일한 문장이 출력된다. 이것이 바로 클래스가 객체를 생성하는 설계도인 이유이다.\n동일한 설계도에서는 항상 동일한 객체가 생성된다.\nUML 모델링 UML을 이용해서 Student 클래스를 표기한 결과는 위와 같다. 가장 위에서부터 구획별로 클래스명(ClassName), 속성(Property), 연산(Operation)을 기술한다. 만약 속성이나 연산이 없다면 생략할 수 있다.\n접근 제어자 접근 제어자 표시 설명 public + 어떤 클래스의 객체든 접근 가능 private - 이 클래스에서 생성한 객체들만 접근 가능 protected # 이 클래스와 동일 패키지에 있거나, 상속 관계에 있는 하위 클래스의 객체들만 접근 가능 package ~ 동일 패키지에 있는 클래스의 객체들만 접근 가능 클래스의 속성과 연산을 정의할 때 -나 +와 같은 기호를 사용하는데, 이것은 가시화를 정의하는 접근 제어자이다. 자바에서의 private과 public를 위와 같이 표기한다고 생각하면 된다.\n속성과 연산 구분 표기법 속성 [접근 제어자]이름: 타입[다중성 정보] [=초기값] 연산 [접근 제어자]이름(인자: 타입): 리턴 타입 속성과 연산을 표기하는 형식은 위와 같다. 클래스 다이어그램은 개념 분석 단계에서 구현에 이르기까지 광범위하게 사용되는데, 분석 단계에서는 속성이나 연산을 구체적으로 표현하기 보다는 정의하는 것이 주를 이룬다. 이후 설계 단계에서 구체적인 타입 정보나 가시화 정보를 기술하게 된다.\n[] 안에 들어있는 것은 생략해도 괜찮다는 의미이다.\n관계 객체 지향 프로그래밍에서 객체 하나만을 사용하는 경우는 드물다. 보통 기능별로 객체를 나누어 지고 이들의 상호작용이 하나의 소프트웨어를 동작하게 한다. 이러한 클래스 간의 관계를 UML에서는 아래와 같이 표현한다.\n연관 관계 클래스들이 개념상 연결되어 있음을 의미하며 실선으로 표시한다.\n양방향 연관 관계 교수와 학생 클래스의 연관 관계와 같이 서로를 인식하는 경우를 양방향 연관 관계라고 하며, 화살표 없는 실선으로 표기한다.\n만약 이들이 상담한다는 것을 나타내고 싶다면 위와 같이 실선 상단에 명시해주면 된다. 연관 관계에서의 역할 이름(rule name)도 정할 수 있는데 실선의 양 끝단에 정의해주면 된다. 이것은 이후 프로그램을 구현하는 단계에서 서로를 참조하는 속성으로 활용될 수 있다.\n단방향 연관 관계 학생 한 명은 여러 수업을 수강할 수 있다. 이것을 UML을 통해서 모델링한다면 다음과 같이 표기할 수 있다.\n이때 화살표는 학생에서 수업으로 향하는데 이는 학생이 수업을 인식하고 있음을 의미하며, 반대로 수업은 학생을 인식하지 못한다. 이러한 경우를 단방향 연관 관계라고 한다.\n위 다이어그램에는 1..*이라는 표기가 있는데 이것은 다중성을 나타낸 것이다. 다중성은 연관되어 있는 객체의 수를 의미한다.\n*은 0 이상을 의미하며, ..은 범위를 나타낸다. 따라서 1..*는 1 이상이라는 의미이다. 객체가 하나인 경우는 생략하기도 한다.\n다대다 연관 관계 앞선 예시를 잘 생각해보면 뭔가 이상하다는 사실을 깨닫을 수 있다. 실세계에서는 한 명의 학생만이 수업을 수강하는 경우는 없다. 보통은 다수의 학생이 다수의 수업을 수강한다. 이것을 표현하면 아래와 같은 그림이 나올 것이다.\n이렇게 다수의 객체 - 다수의 객체가 가지는 관계를 다대다 연관 관계라 하며, 이것은 일반적으로 UML에서 양방향 연관 관계로 표현된다.\n그런데 만약에 학생이 수업을 수강하면서 발생하는 성적 정보를 저장하고 싶다면 어디에 저장해야 할까? 학생이나 수업 클래스에 그대로 성적 정보를 저장한다면 다음과 같이 표현될 것이다.\n홍길동 학생이 A+이다 또는 객체 지향 모델링 수업에서 A+을 받았다\n다만 여기에는 어떤 수업에서 누가 해당 성적을 얻었는가에 대한 정보가 빠져 있다. 따라서 학생 성적은 학생이나 수업 클래스에 저장하는 것보다는 별도의 클래스를 만들어 저장하는 것이 옳다. 이때 사용되는 Transcript와 같은 클래스를 연관 클래스라 한다.\n연관 클래스의 실제 구현은 일반 클래스의 단방향 연관 관계로 변환되어 이루어진다.\n실제로 프로그램을 구현할 때, 양방향 연관 관계는 사용되지 않는다!\n재귀적 연관 관계 연관 관계는 때로는 재귀적이다. 예를 들면 군대에는 선임과 후임이라는 관계가 존재한다. 내게 선임인 군인도 누구에게는 후임이며, 내게 후임인 군인도 누군가에겐 선임이다.\n이러한 경우 군인이라는 클래스는 선임과 후임이라는 두 클래스에 동시에 속하는 모순이 발생한다. 하지만 그렇다고 해서 두 클래스를 별도로 만드는 것은 유연성이 부족하다. 이러한 경우 재귀적 연관 관계가 사용된다.\n하지만 재귀적 연관 관계에는 관계의 루프라는 문제가 남아 있다. 예를 들어 가위바위보는 가위가 보를 이기고 보는 바위를 이기고 바위는 가위를 이기는 게임이다. 이렇게 루프가 존재하는 경우는 {계층}으로 제약을 설정하여 배제해야만 한다.\n{계층}은 객체 사이에는 상하 관계가 존재하며, 사이클이 존재하지 않음을 의미한다.\n일반화 관계 한 클래스가 다른 클래스를 포함하는 상위 개념일 때 두 클래스 간의 관계이다.\n자식 클래스(서브 클래스)는 부모 클래스(슈퍼 클래스)로부터 속성이나 연산을 물려 받을 수 있다. 그렇기 때문에 일반화 관계를 상속 관계라고도 한다.\n보통 일반화 관계는 is-a-kind-of 관계라고 말한다. 가전 제품과 세탁기의 관계는 세탁기 is-a-kind-of 가전 제품라고 설명할 수 있다.\n집합 관계 집합 관계는 연관 관계의 특별한 경우로 전체와 부분의 관계를 명확하게 명시하고자 할 때 사용한다. 집약(aggregation)과 합성(composition) 두 종류의 집합 관계가 존재한다.\n집약 관계 한 객체가 다른 객체를 포함하는 것을 나타낸다.\n전체를 가리키는 클래스 방향에 빈 마름모 표시 부분 객체를 다른 객체와 공유할 수 있음 전체 객체와 부분 객체의 라이프타임은 독립적 합성 관계 부분 객체가 전체 객체에 속하는 관계이다.\n전체를 가리키는 클래스 방향에 채워진 마름모 표시 부분 객체를 다른 객체와 공유할 수 없음 부분 객체의 라이프타임은 전체 객체에 의존 차이점 집약 관계와 합성 관계는 얼핏보면 비슷해 보이지만 큰 차이를 가지고 있다. 가장 중요한 차이는 라이프타임이다. 전체 객체가 소멸되었을 때 부분 객체가 남아 있다면 그것은 집약 관계이다. 반대의 경우는 합성 관계라고 생각하면 된다.\n컴퓨터를 조립하는 예시를 가지고 이 둘의 차이를 알아 보겠다.\npublic class Computer { private MainBoard mainBoard; private CPU cpu; private Memory memory; private PowerSupply powerSupply; public Computer(MainBoard mainBoard, CPU cpu, Memory memory, PowerSupply powerSupply){ this.mainBoard = mainBoard; this.cpu = cpu; this.memory = memory; this.powerSupply = powerSupply; } } 컴퓨터 객체는 외부에서 만들어진 메인보드, CPU, 메모리, 파워서플라이를 받아서 생성된다. 따라서 컴퓨터 객체가 소멸되더라도 컴퓨터를 구성하는 부분 객체들은 사라지지 않고 메모리에 남아 있게 된다. 따라서 위 소스코드는 집약 관계를 나타낸 것이라는 사실을 알 수 있다.\npublic class Computer { private MainBoard mainBoard; private CPU cpu; private Memory memory; private PowerSupply powerSupply; public Computer(){ this.mainBoard = new MainBoard(); this.cpu = new CPU(); this.memory = new Memory(); this.powerSupply = new PowerSupply(); } } 이전 예시와 달리 위 코드는 컴퓨터를 생성하는 동시에 구성품들이 생성된다. 따라서 해당 요소들의 생명주기는 전체 객체에 의존하게 된다. 컴퓨터가 소멸되는 동시에 부분 객체들도 사라지기 때문에 합성 관계라고 볼 수 있다.\n의존 관계 다른 클래스에서 제공하는 기능을 사용할 때 나타나는 관계이다.\n일반적으로 한 클래스가 다른 클래스를 사용하는 경우 다음 3가지이다.\n클래스의 속성에서 참조 연산의 인자로 사용 메서드 내부의 지역 객체로 참조 사람이 차를 소유하고 있고, 이 차는 주유소에서 충전한다는 것을 다이어그램으로 나타내면 위와 같다. 이때, 사람-차 간의 관계는 연관 관계이지만 차-주유소 간의 관게는 의존 관계이다.\npublic class Person { private Car car; public void setCar(Car car){ this.car = car; } ... } public class Car { public void fillGas(GasPump p){ p.getGas(amount); ... } } 사람이 타고 다니는 차는 매번 변화하는 것이 아니므로 Person클래스의 속성으로 Car객체를 참조한다. 반면 차를 주유하는 주요소는 매번 같지 않으므로 인자나 지역 객체를 통해 구현한다.\n실체화 관계 인터페이스와 이것의 책임들을 실체화한 클래스 간의 관계이다.\n책임이란 객체가 해야 하는 일 내지 할 수 있는 일을 말한다.\n인터페이스 자체는 실제로 책임을 수행하는 객체가 아니다. 실체화를 통해서 만들어진 객체가 인터페이스에 정의된 책임을 수행하게 된다.\n예를 들어 날기 위한 책임을 담은 Flyable이라는 인터페이스를 실체화한 Plane과 Bird 클래스는 해당 인터페이스의 책임을 구현해야만 한다. 이러한 점에서 인터페이스는 어떤 공통되는 능력이 있는 것들을 대표한다는 관점으로 볼 수도 있다. 그렇기 때문에 실체화 관계를 can-do-this 관계라고 부른다.\n참고문헌 정인성, 채흥석,『JAVA 객체지향 디자인 패턴』, 한빛미디어 ","permalink":"https://vanslog.io/ko/posts/cs/uml/class-diagram/","summary":"\u003ch2 id=\"클래스란\"\u003e클래스란?\u003c/h2\u003e\n\u003cp\u003e클래스는 동일한 속성과 행위를 수행하는 객체들의 집합이다. 예를 들어 소프트웨어를 전공중인 학생들의 공통점은 \u003ccode\u003e소프트웨어를 전공한다\u003c/code\u003e는 사실과 \u003ccode\u003e동일한 전공 수업\u003c/code\u003e을 듣는다는 점이다. 이러한 경우 \u003ccode\u003e소프트웨어 전공 중인 학생\u003c/code\u003e은 실제 학생들의 클래스라고 말할 수 있다.\u003c/p\u003e\n\u003cp\u003e클래스를 정의하는 또 다른 관점은 \u003ccode\u003e인스턴스(객체)를 생성하는 설계도\u003c/code\u003e로 보는 것이다. 아래 소스코드는 소프트웨어를 전공하는 학생 클래스를 정의한 것이다. 클래스가 객체를 생성하는 설계도라는 관점에서 코드를 한 번 살펴보자\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eStudent\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emajor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;SW\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eStudent\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003estudy\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;객체 지향 모델링 수업을 수강합니다.\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eStudent\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estudent1\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eStudent\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;학생1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eStudent\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estudent2\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eStudent\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;학생2\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003estudent1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003estudy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003estudent2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003estudy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e위 코드를 통해서 같은 스펙을 가진 학생 객체가 두 개 생겨났다. 두 객체는 이름을 제외하면 모두 동일한 특성을 가진다. major 속성은 \u003ccode\u003eSW\u003c/code\u003e로 동일하며 \u003ccode\u003estudy()\u003c/code\u003e 메소드를 실행하면 동일한 문장이 출력된다. 이것이 바로 클래스가 객체를 생성하는 설계도인 이유이다.\u003c/p\u003e","title":"[UML 2.0] 클래스 다이어그램(Class Diagram)"},{"content":"개념 요구 분석, 시스템 설계, 시스템 구현 등의 시스템 개발 과정에서 개발자 사이의 의사 소통이 원활하게 이루어지도록 표준화한 통합 모델링 언어(United Modeling Language)\n다이어그램으로 프로그램을 설명하는 UML이 처음이 아니다. 과거에는 객체 지향 모델링을 위해 OMT를 사용하였는데 이것이 UML의 원형이 되었다.\n다이어그램 구조 다이어그램 다이어그램 유형 목적 클래스(Class) 시스템을 구성하는 클래스들 사이의 관계 표현 객체(Object) 객체 정보를 보여줌 복합체 구조(Composite Structure) 복합 구조의 클래스와 컴포넌트 내부 구조 표현 배치(Deployment) S/W, H/W, N/W를 포함한 실행 시스템의 물리 구조 표현 컴포넌트(Component) 컴포넌트 구조 사이의 관계 표현 패키지(Package) 클래스나 유스 케이스 등을 포함한 여러 모델 요소들을 그룹화해 패키지를 구성하고 패키지들 사이의 관계를 표현 행위 다이어그램 다이어그램 유형 목적 활동(Activity) 업무 처리 과정이나 연산이 수행되는 과정 표현 상태 머신(State Machine) 객체의 생명주기 표현 유스 케이스(Use Case) 사용자 관점에서 시스템 행위를 표현 상호작용(Interaction) 목적에 따라 4가지로 분류됨 상호작용 다이어그램 다이어그램 유형 목적 순차(Sequence) 시간 흐름에 따른 객체 사이의 상호작용 표현 상호작용 개요(Interaction overview) 여러 상호작용 다이어그램 사이의 제어 흐름을 표현 통신(Communication) 객체 사이의 관계를 중심으로 상호작용 표현 타이밍(Timing) 객체 상태 변화와 시간 제약을 명시적으로 표현 UML 2.0에서는 시스템 구조와 동작을 표현하는 13개 다이어그램을 제공한다. 이렇게 많은 종류가 존재하는 이유는 다양한 관점으로 시스템을 모델링하기 위함이다.\n특징 가시화 언어 모델링 결과를 가시적으로 나타냄 명세화 언어 정확하고 완전하게 모델링 하는 것 구축 언어 시스템을 구축할 수 있게 함 문서화 언어 시스템에 대한 통제, 평가, 의사소통의 역할 도구 draw.io 무료 웹 기반 UML 드로잉 애플리케이션 starUML 무료 데스크톱 기반 UML 드로잉 애플리케이션 참고문헌 정인성, 채흥석,『JAVA 객체지향 디자인 패턴』, 한빛미디어 ","permalink":"https://vanslog.io/ko/posts/cs/uml/what-is-uml/","summary":"객체 지향 애플리케이션을 모델링하기 위한 언어, UML","title":"[UML 2.0] UML이란?"},{"content":"이번년도는 돌이켜보면 참으로 다사다난한 해였다. 1년동안 활동한 멋쟁이 사자처럼 7기를 무사히 마치고, 8기 운영진을 선발했다. 그리고 운영진 교육을 진행하면서 취업 준비를 동시에 준비하였다. 실제로 산업체 복무에 성공하기도 했지만, 결국에는 퇴사하고 군대에 오기까지 별에 별 일이 다 있었다. 2020년은 내게 여러모로 아쉬움이 남는 해이다. 이번 회고를 통해 부족한 점을 돌아보고 개선하고자 한다.\n멋쟁이 사자처럼 목표는 중요하다. 중학생 때부터 나는 줄곧 컴퓨터를 좋아했고, 네이버 블로그를 통해 IT 관련 지식을 공유하기도 하였다. 그러다보니 자연스래 코딩에 대한 관심을 가지게 되었다. 하지만 그렇다고 코딩에 본격적으로 빠져든 것은 아니었다. 첫째로 처음 접한 C언어가 너무 어려웠고 둘째로 뭔가를 하고 싶다는 목표가 뚜렷하게 없었기 때문이다.\n운영진 활동을 하며 꿈을 찾다. 그런 나에게 있어서 멋쟁이 사자처럼은 내게 있어서 단비같은 존재였다. 멋사는 대학교 1학년 때 사귄 친구의 권유로 알게 되었고, 덕분에 7기 운영진까지 지원하였다. 처음에는 사람들 앞에 나서서 수업을 진행해야 한다는 점 때문에 운영진을 하고 싶지 않았었다. 하지만 지금 돌이켜보면 운영진 활동을 하게 된 것은 내게 큰 도움이 되었다. 운영진이라는 역할을 해내이기 위해서는 수많은 공부를 필요로 했는데, 이것이 나를 끊임없이 발전시키는 원동력이 되었다.\n멋사 7기 운영진으로의 활동은 2020년 초 수료하는 것으로 무사히 마무리를 지었다. 7기 운영을 하면서 해커톤에서 특별한 성적을 얻지는 못했지만, 수업을 진행하는 과정 자체가 내겐 큰 의미였다. 수업 교안을 준비하면서 나는 백엔드 개발자라는 목표를 가지게 되었으며, 코딩이 어렵지 않고 재미있다고 생각하게 되었기 때문이다. 이때부터 나는 본격적으로 프로그래머의 꿈을 꾸기 시작하였다.\n아쉬운 점 나는 경험 상 프로그래밍은 이론이나 개념도 중요하지만 직접 만들어보면서 느끼고 배우는 것이 컸다. 그래서 수업 진행을 PPT위주가 아닌 실습 위주로 진행하였고, 필요한 개념은 Notion에 정리한 내용과 판서를 통해서 설명하였다. 당시 이러한 방식이 학습하는 데 효율적이다라고 생각하고 진행했는데, 수업을 잘 이해하지 못하는 사람들도 있었을 것이다. 수업을 준비하면서 학업까지 동시에 진행하다보니 수업 진행에 있어서 준비가 부족한 점이 많았다고 생각하고 이 부분에 대해서는 여전히 동아리원들에게 미안하다.\n군대 대신 취업을 결심하다. 계기 나는 군대를 가기 싫었다. 이제 막 백엔드 개발자라는 꿈을 설계하기 시작했는데, 이것을 잠시 내려두고 어디론가 가야 한다는 것 자체가 내겐 어려운 일이었다. 계속해서 나는 앞으로 나아가고 싶었고, 그것을 위한 방법이 바로 산업체에 취직하는 것이었다. 산업체에 들어가면 경력 단절이 없음은 물론 3년의 복무 기간동안 군복무와는 비교도 안되는 월급을 받을 수 있다는 장점도 있었다. 무조건 회사에 들어가야 한다는 생각으로 3학년 1학기를 휴학하였다. 멋쟁이 사자처럼 8기 운영진 교육을 진행하면서 동시에 취업 준비를 하고 있었다.\n취준생이 되다. 몇 개월에 걸친 취업 준비 끝에 성공할 수 있었다. 취업에 필요한 정보들은 보통 Awesome Alternative Military Service와 미필자 정보공유 카페에서 얻을 수 있었고, 입사 지원은 대개 로켓펀치와 산업지원 병역일터에서 이루어졌다. 이곳들에 올라오는 공고를 보고 여러 회사에 지원한 결과 한 회사에서 연락이 돌아왔고, 취업의 기회를 얻을 수 있었다.\n취업에 성공하다. 일주일 동안 해야하는 과제는 플라스크를 기반으로 하는 API 서버를 구축하고, postgresql와 sqlalchemy, redis를 이용해서 데이터와 사용자 세션을 관리하는 간단한 서비스를 만들어보는 것이었다. 해당 기간동안 나는 과제를 하는 데에만 집중한 채 다른 것은 미루어 두었다. 요구사항에 기재된 기술 스택들이 내게는 생소한 것들이었기 때문이다. Docker 컨테이너 상에 웹서버, WAS서버, DB서버마저 올려야 하는 상황에서 나는 모르는 것이 너무나 많았다.\n당장 구축해야하는 도커를 사용하지 못하는 상황에 나머지 기술들도 사용해본 적도 없었다. 그래서 일주일을 오로지 투자하여 잠 자는 시간을 빼고는 개발에 전념했다. 그 결과 모든 요구사항을 만족하는 API서버를 구축할 수 있었다. 짧은 입사 테스트 기간동안 배운 것들이 너무나 값지다 생각했기에 떨어지더라도 후회가 없었다. 게다가 이후 면접에서 많은 질문에 대답을 잘 못했기 때문에 분명 떨어질거라 생각하고 반쯤 포기하고 있었다. 내 예상과는 달리 나는 입사 테스트에 합격해 있었다.\n합격 이메일이 스팸함이 있었다는 사실을 몰라 하마터면 자동으로 탈락될 뻔 했다.\n회사 생활에서 얻은 것들 회사는 강남에 위치한 공유 오피스에 있었다. 학교가 아닌 회사로 출퇴근하는 풍경은 새로웠고 설레는 일이었다. 회사에서는 맥북 15인치과 델 모니터 하나를 제공해주는 점도 마음에 들었다. 회사에 들어가자마자 일을 하지는 않았고 약 10일 동안 부여된 적응기간이 내게 주어졌다. 이 기간 동안 깃랩에 있는 서버 소스코드를 개발 서버에서 구동해보는 과제를 수행하며 서비스에 대한 이해를 높여 갔다.\n이후에는 테스트코드를 작성하는 작업을 하였고, 이슈에 등록되어 있는 버그를 조치하였다. 가장 기억에 남는 작업은 회사에서 마지막으로 한 작업인 슬랙봇 기능개선이다. 서비스 기능 중에 채팅이 있었는데 해당 기능은 사용자들이 서비스를 이용하다가 궁금한 점을 물어보기 위한 것이다. 사용자는 채팅을 시작(open)한 이후 더 이상 질문을 이어나갈 필요가 없어지는 경우 채팅을 닫고(close) 그만두게 되는데, 이러한 방식은 깃헙에 이슈를 생성하고 그 아래에서 댓글을 이어나가는 것과 유사하다.\n회사에서는 이러한 사용자들의 채팅(문의사항)들을 효과적으로 관리하기 위해서 슬랙봇으로 슬랙 채팅방에 동일한 메시지를 전송하게 하였다. 하지만 해당 기능은 사용자가 보내는 메시지를 하나씩 슬랙에 출력하는 바람에 내용을 한 눈에 파악하기 힘들었다. 다시 말해 하나의 문의에 해당하는 채팅이 여러 개로 분리된 모습은 그리 가독성이 좋지는 않았다.\n그래서 동일한 문의에 대한 메시지들은 하나로 합쳐서 보여주는 기능을 만들자는 깃허브 이슈가 올라와 있었고, 나는 이것을 해결하는 역할을 맡았다. Slack API를 찾아보니 메시지를 merge하는 기능은 없었다. 대신에 chat.update와 search.messages를 이용해서 새로운 메시지가 이미 존재하는 문의에 해당하는 것이라면 합쳐서 보여주고, 그렇지 않다면 새로운 메시지를 만들 수 있도록 하는 방법으로 해결하였다.\n결국 퇴사하다. 회사를 다닌지 한달이 될 무렵, 사수분이 나와 직장 동료를 조용히 불렀다. 우리들처럼 회사에는 산업요원으로 복무를 희망하는 사람들이 많고, 그들 중 한두명을 선발하는데 그것이 우리가 아니라는 이야기였다. 회사를 더 다니고 싶다면 다녀도 좋지만 그렇지 않다면 회사를 나가도 좋다고 하였다.\n내가 회사에 입사하게 된 목적은 돈을 벌기 위한 것도 있었겠지만, 군복무를 대체하기 위한 목적이 가장 컸기에 이러한 제안은 내게 결과적으로 회사를 나가달라는 말과 다름없었다. 나는 결국 짤린.. 아니 퇴사를 결심하게 된 것이었다.\n그때 당시 내가 회사를 나와야 했던 이유는 다음과 같다.\n부족한 기여도 시간 약속 지키기 열정(?) 기억이 정확하게 다 나지는 않지만 사수가 말하길 스타트업에서는 신입이더라도 충분히 성장하기를 기다려줄 여유는 없고 바로 성과를 낼 수 있는 인재를 원한다고 했다. 나는 어떠한 업무를 실행할 때 기간을 설정하고 지키는 과정이 잘 이루어지지 않는다는 부분으로 자주 지적받았다. 또한 성과를 못내면 늦게까지 회사에 남아서 일하는 열정이라도 보여주어야 했다는 말도 들었다.\n이러한 사수분의 말들은 결국 내게 따끔한 일침으로 돌아왔고, 진심으로 내가 부족한 부분을 돌아 볼 수 있는 계기가 되었다. 다음에 회사에 입사할 때는 늦은 시간까지 남아서 일하지 않아도 회사에서 한 몫을 다할 수 있는 능력있는 사람이 되어야겠다고 다짐한다.\n당시 회사에서 나오면서 인생의 쓴 맛을 맛보았지만, 아이러니하게도 그 당일에 입금된 첫 월급은 무척 달았다.\n이번년도의 끝은 군복무 다시 회사에 지원했으나 결국에 회사를 그만두고 할 수 있는 것은 다음 회사를 찾거나 군대를 가는 것이었다. 이후에도 몇 차례 회사에 지원했지만 가고 싶은 회사에 붙지는 못했다. 한 회사에서는 내가 맘에 들었는지 다음년도(2021년)에 나오는 TO를 줄테니 회사에 다니면서 기다려보라고 이야기했다. 심지어는 취준을 병행해도 된다는 파격 조건을 내걸었으나 내가 싫다고 하고 기회를 걷어찼다. (지금 생각해보면 왜그랬는지 모르겠다..) 그리고 다른 회사는 면접을 가면 붙을 거 같기는 했는데 내 커리어에 도움될 것 같지는 않은 회사라서 가지 않겠다고 정중히 거절했다.\n정보보호병 지원 나는 앞서 말했다싶이 군대를 가기 싫어하던 이유가 경력단절에 있다. 이제 꿈을 찾아서 앞으로 나가가려는 나에게 있어서 군대는 장애물에 불과했다. 부모님이 아무리 남자는 군대를 가야한다는 말을 해도 들리지 않았다. 하지만 확실하게 산업체에서 일할 수 있는게 아닌 만큼 끝없이 휴학하고 집에만 있을 수는 없는 일이었다. 그래서 결국에는 군대를 선택하게 되었고, 정보보호병이라는 그래도 전공과 연관이 있는 보직에 지원하게 되었다. 게다가 사단보다는 군단 이상 급으로 자대가 나온다는 이야기를 듣고 많은 기대를 가지고 있었다.\n자대배치, 절망 정보보호병 중에서도 다섯 손가락에 드는 성적이었음에도 불구하고, 나는 28사단에 배정받게 되었다. 후반기 때 친구들이 다 지작사나 군단을 간 것을 생각하면 아직도 배가 아프다. 후반기 마지막 주차 때는 거의 정신 나간 채로 살았던 거 같다. 왜 내가 무엇때문에 사단으로 배정이 난 것일까 계속 생각했다. 그 결과 결국 깨달았다. 도박은 하지 말자. 그게 내가 내린 결론이다. 내 인생은 내가 개척하는 것이지 군대처럼 내가 어디갈지 모르는 것에 몸을 맡기니 이렇게 되는 것이라고, 후회해도 어쩔 수 없다. 군대에 지원한 결정 자체는 내가 내린 것이기에\u0026hellip;\n앞으로의 목표 군대에 온 이상 일정 시간이 지나가기 이전에는 절대로 나갈 수 없다. 그것이 군대의 룰이다. 그렇다면 그 시간 안에 얼마나 많은 것을 해낼 수 있느냐가 중요한 관건이다. 계획을 세우고 이것을 실행하는 것으로 후회없는 군생활을 보내고 싶다. 아래는 그것을 위한 개략적인 목표이다.\n꾸준한 블로그 활동 코딩 테스트 준비 백준 문제 등 활용 소프트웨어 아키텍처 공부하기 GoF 디자인 패턴 Java 객체지향 디자인 패턴 클린 아키텍처 애자일 공부하기 리팩토링 클린 코드 부족한 자바 공부 이펙티브 자바 이외에 60권 독서 100일 동안 다이어트 및 운동 군대라는 공간 특성상 코딩을 제대로 하기란 쉽지 않고, 나는 전역 이후의 내가 지금보다 더 나은 개발자가 되어있기를 원한다. 그렇기에 개발자로서 갖추어야 할 지식들을 군대 내에서 최대한 많이 공부하기로 결정했다. 그리고 여기에는 사람은 끊임없이 공부하는 삶을 영위해야 행복함을 느낀다라고 생각하는 나의 가치관이 드러난다.\n나는 군대에 온 것을 진심으로 후회했다. 특히 자대배치 이후에 말이다. 하지만 또 생각해보면 언제 또 이렇게 많은 공부를 할 수 있는 기회가 있을까? 학교나 회사를 다니는 동안에는 바쁘다는 핑계로 하지 못했을 공부를 지금하는 거라 생각하면 좋은 기회이다. 나는 절대로 1년 남은 시간을 가벼이 버릴 생각은 눈꼽만큼도 없다. 내가 결정한 선택의 책임에 있어서 후회는 남기고 싶지 않다.\n","permalink":"https://vanslog.io/ko/posts/retrospective/2020-backend-developer-retrospective/","summary":"2020년을 되돌아보며, 그리고 그 이후","title":"2020년 백엔드 개발자 회고"},{"content":"서론 나는 백엔드 개발자라는 꿈을 가지고 열심히 달려왔다고 생각한다. 그 결과 엘리스라는 산업체에서 짧게나마 복무를 해보기도 했었다. 하지만 지금 생각해보면 백엔드만을 공부하다보니 전반적인 웹에 대한 지식이나 프론트엔드의 트랜드를 많이 놓치고 있지 않았나 하는 생각이 들었다. 군대에 와서는 블로그도 만들어보는 과정에서 프론트엔드 단의 개발에 관심이 생겼다. 그렇다고 지금 프론트 개발자로 전향하겠다 선언하는 것은 아니다. 단지 백엔드 개발자라는 이유로 프론트를 외면해서는 안되겠다는 생각을 하게 된 것이다. 그래서 이번 글을 통해서 최근 웹 프론트엔드에서 트랜드인 용어들(SSR, CSR, SPA, MPA)를 공부해보기로 했다.\nStatic Page 정적 페이지란 웹 서버 상에 저장된 파일(HTML, JS)이 사용자에게 그대로 전달되는 웹 페이지를 말한다. 데이터가 직접 변경되지 않는 한 페이지는 동일하게 보여지며 빠른 렌더링 속도를 가진다. 개인 블로그와 같이 정해진 내용을 렌더링하는 사이트를 만드는 경우에는 정적 페이지가 많이 사용된다. 보통은 정적 사이트를 처음부터 만드지 않고 Static Site Generator를 이용하는데 대표적인 툴로는 jekyll, hexo, gatsby, hugo 등이 있다.\nDynamic Page 말 그대로 동적인 웹페이지를 말한다. 정적 페이지와 달리 애플리케이션에서 전달하는 데이터에 따라 동일한 페이지여도 다른 정보를 표현할 수 있다. 예를 들어 페이스북에 접속했을 때 사용자별로 보이는 정보는 각자 다르다. 하지만 분명히 동일한 페이지(www.facebook.com)에 접속하고 있다는 사실은 모두가 잘 알고 있을 것이다. 현대의 대다수의 웹페이지는 이러한 방식을 통해 만들어지고 있다.\nCSR(Clent-Side Rendering) 클라이언트 단에서 렌더링을 하는 방식을 말한다. 브라우저 상에서 HTML, JS 등 렌더링을 위해 필요한 것들을 모두 다운로드하고 렌더링한다. 이후에 필요한 데이터만 서버에 요청해서 JS로 동적으로 처리한다.\nSSR(Server-Side Rendering) 전통적인 웹 애플리케이션의 동작방식이다. 서버 상에서 렌더링을 마치고 클라이언트는 이것을 받아서 실행한다. CSR과 달리 페이지를 처음 접속할 때만이 아니라 다른 페이지로 이동할 때마다 새로고침이 일어난다.\nSPA(Single Page Application) 한 개의 페이지로 이루어진 애플리케이션이다. 이 방식으로 만들어진 사이트는 접속하는 처음에만 페이지를 로딩하고 이후는 필요한 데이터만 서버에 요청해서 업데이트한다. 그래서 페이지를 새로고침하지 않는다는 특징을 가진다. 최근 프론트엔드 개발에서 엄청난 인기를 받고 있는 라이브러리인 리액트가 대표적인 예시인데, 페이스북은 이것을 통해 만들어진 대표적인 사이트이다. 실제로 페이스북을 웹에서 사용해보면 접속하는 처음만 페이지 로딩이 일어나고 이후에는 새로고침 과정이 전혀 일어나지 않는다.\nMPA(Single Page Application) 여러 페이지로 이루어진 애플리케이션을 말하며 SPA와 상반되는 개념이다. 서버로부터 완전한 페이지를 받아오며 다른 데이터를 보기 위해서는 브라우저의 새로고침이 필요하다.\nSPA != CSR 위 내용을 읽어보면 SPA와 CSR이 유사한 개념이라는 사실을 알 수 있을 것이다. 그렇다고 같은 개념이라고 오해해서는 안된다. SPA(Single Page Application)을 구현하기 위해서 CSR(Client-Side Rendering) 방식이 사용되는 것이지 등식이 성립하는 관계는 아니다. 굳이 따지자면 SPA를 이루는 요소 중 하나가 CSR(SPA⊃CSR)이라고는 할 수 있겠다.\nMPA/SSR의 관계도 SPA/CSR의 것과 동일하다. MPA를 구현하기 위해 SSR이 사용된다.\nSPA vs MPA 구분 SPA MPA 장점 컴포넌트 재사용유연한 UI SEO에 유리 단점 초기 페이지 로딩 시간SEO에 불리 다른 페이지 이동 시 새로고침 검색 엔진 최적화 문제 SEO(Search Engine Optimization)는 검색엔진 상에서 검색 결과의 상위에 나올 수 있도록 하는 작업을 말한다. 보통 검색엔진은 웹 사이트들을 크롤링한 결과를 보여주는데 구글을 제외한 대다수의 검색엔진 크롤러는 JS를 해석하지 못한다. MPA는 서버에서 이미 렌더링된 페이지를 그대로 보여주므로 SEO 문제가 발생하지 않는 반면, 빈 페이지를 로딩한 이후 데이터를 JS를 통해 렌더링하는 SPA는 검색엔진이 적절히 크롤링하지 못한다.\n결론 오늘날 우리가 사용하는 웹 페이지는 다양한 구조를 가지고 있다. 전통적인 방식으로는 웹서버 상에 위치한 자원을 그대로 보여주기만 하는 정적 페이지 그리고 서버에서 렌더링한 페이지를 JSP/Servlet 등을 통해 제공하는 방식이 있었다. 하지만 최근엔 다양한 요구사항에 따라 앱처럼 동작하는 웹 애플리케이션의 필요로 인해 SPA가 탄생했다. 이러한 패러다임의 변화로 렌더링이 이루어지는 위치에 따라 SSR과 CSR로 구분짓기 시작했다.\n사실 따지고 보면 CSR이라는 개념은 전혀 새로운 것은 아니다. 전통적인 MPA이더라도 일부 페이지 내용을 Ajax를 통한 비동기 통신으로 변경하는 경우가 있었기 때문\n최근에는 SEO 문제를 해결하기 위해서 SPA에 SSR을 도입하는 경우가 있다. 대표적인 예로는 리액트 기반의 프레임워크인 next.js이다. 이것을 이용하면 CSR은 물론이고 SSR까지 지원할 수 있게 된다. 이렇게 사용하는 방식을 hybrid라고 부른다. SPA를 유지하면서 검색엔진 최적화를 하고 싶다면 react-helmet이나 react-snap 등을 이용하면 된다.\n결과적으로 말하고 싶은 것은 웹 애플리케이션을 구현하는 방식은 다양하다. 어느 게 정답이라 할 수 없고 여러 방식을 혼합해서 사용해도 좋다. 결국에는 주어진 과제에 맞게 풀어나가는 것이 중요하다고 생각한다. 검색 엔진 최적화가 우선시 되어야 하는 경우 SSR를 도입해야 할 것이며, 웹 상에서 유연한 UI와 새로고침 없는 앱과 같은 사용자 경험을 제공하고 싶다면 CSR기반의 SPA를 도입해야 할 것이다.\n","permalink":"https://vanslog.io/ko/posts/web/web-application-structure/","summary":"최근 웹 프론트엔드에서 트랜드인 용어를 정리해보았다.","title":"웹 애플리케이션 구조(SSR, CSR, SPA, MPA)"},{"content":"다층구조 아키텍쳐는 일반적으로 클라이언트-서버 애플리케이션에서 사용된다. 웹 애플리케이션을 비롯한 대다수 애플리케이션에서 활용되는 기본적인 구조인 만큼 익혀두어야 하는 개념이라 생각한다.\nlayer n-tier architecture를 구성하는 계층(layer)은 아래 3가지이다.\n프레젠테이션: 유저 인터페이스(UI) 애플리케이션(또는 로직): 비즈니스 로직을 처리함 데이터: 데이터베이스를 의미 여기서 주의해야 할 점이 층(tier)와 계층(layer)은 구분되는 개념이다. 층은 물리적으로 분리된 것을 의미하며 계층은 논리적으로 분리되는 개념이다.\n2-tier vs 3-tier 2-tier 자바 등을 통해서 구현된 클라이언트 프로그램 직접 DB에 접속하여 가져온 데이터를 표현한다. 3-tier 웹 애플리케이션이 대표적인 예시 DB에 직접 접속하지 않고 미들웨어에 접속 참고로 2-tier 구조는 꼭 클라이언트 프로그램이 아닌 웹 애플리케이션으로 구현될 수도 있다. 또한 반대로 클라이언트 프로그램도 3-tier로 구현하는 것도 가능하다.\n구현 사례 내가 대학교 2학년 때 전공 프로젝트로 진행한 대학 수강신청 시스템은 다층구조를 설명하기 좋은 사례이다. 2학기에 걸쳐 구현한 이 프로그램은 1-tier 구조에서 시작해서 3-tier로 진화시켰다. 아래는 프로그램 버전별 특성을 간략하게 정리한 것이다.\nv1.0 1-tier Program 로컬에서 실행되는 간단한 클라이언트 프로그램 수강신청 관련 데이터 파일을 담아서 불러오게 함. UI, 비즈니스 로직, 데이터 모두 한 곳에 있음 v2.0 2-tier Program 클라이언트 프로그램은 단순히 프레젠테이션 기능만 갖춤 서버 프로그램을 만들어서 클라이언트 요청 처리 클라이언트는 UI만을 표현, 서버에서 로직 및 데이터 처리 v2.1+ 3-tier Program 데이터 파일을 MySQL DB로 마이그레이션 해당 DB는 aws-rds에 deploy 모든 기능이 물리적으로 분리된 구조 지금와서 되집어보니 작년에 프로젝트를 진행할 당시에는 해당 프로젝트가 가진 진가를 몰라봤었다고 생각한다. 교수님이 진행하는 수업에 따라 열심히 만들기는 했지만 그 안에 담긴 내용을 모두 이해하지는 못하고 있었다고나 할까? 1년 동안 진행한 프로젝트에 다층 구조의 진화과정이 있었다는 사실을 왜 진작에는 몰라봤을까..\n마치며 이번 포스팅을 통해서 배움이라는 것이 꼭 새로운 것을 아는 것만이 아니라는 것을 알게되었다. 이전에 진행한 프로젝트를 되집어보는 리뷰 과정도 충분히 의미있는 시간이 될 수 있다. 물론 처음 배울 때 모든 것을 깨달을 수만 있다면 그게 최선이겠지만 아쉽게도 나는 아직 그러지는 못하는 것 같다. 군대에 있는 시간동안 블로그에 글을 쓰며 이전에 배운 개념을 정리하고, 앞으로 새로 배울 내용도 정리하고자 한다.\n","permalink":"https://vanslog.io/ko/posts/cs/architecture/n-tier-architecture/","summary":"전공 수업 프로젝트에서 찾은 다층구조 아키텍처","title":"N Tier Architecture"},{"content":"서론 최근 웹으로 접속하는 개발환경인 Web IDE가 활발하게 활용되고 있다. 대표적으로 Cloud9, Codeanywhere, 구름IDE 등이 있는데, 깃헙에서도 Codespace라는 이름으로 비슷한 서비스를 준비중에 있다. 나는 VSCode를 웹에서 바로 사용 가능하다는 점이 마음에 들어 미리보기를 신청했으나 아쉽게도 당첨되지 않았다..\n그래서 방법을 찾던 중 code-server이라는 오픈소스를 찾게 되었다. 해당 서비스는 VS Code를 웹 서버에서 동작할 수 있게 하는 프로젝트이다. MIT 라이선스로 제공되는 오픈 소스인 만큼 조건 없이 사용 가능하다는 것도 장점이다. 군대에서 제약없이 코딩하고자 하는 나에게 있어서 최고의 선택지였다.\n개발 환경 구축 서론이 길었던 것 만큼 설치과정이 길거라고 생각한다면 그것은 큰 착각이다. 설치는 매우 간단하다. 다른 블로그 글을 참고해서 설치해보기도 했지만, 여타 방법보다는 github repo에 나와있는 설치법이 더 쉽고 빠르게 할 수 있다.\n설치 및 실행 curl -fsSL https://code-server.dev/install.sh | sh sudo systemctl enable --now code-server@$USER # 시스템 부팅 시 자동실행 등록 위 코드를 실행하면 code-server를 설치하고 시스템 부팅 시 자동실행이 가능하도록 등록하는 과정까지 마치게 된다. 동시에 code-server는 이미 동작 중에 있겠지만 바로 사용할 수는 없는데, 이는 기본적으로 localhost(127.0.0.1)의 접속만 허용하기 때문이다. 따라서 외부에서 접속 가능하게 하는 설정이 필요하다.\n외부 접속 허용 및 비밀번호 설정 vi ~/.config/code-server/config.yaml 기본적으로 code-server의 설정 파일은 위 경로에 위치한 config.yaml 파일이다.\nbind-addr: 0.0.0.0:8080 auth: password password: #설정할 비밀번호 입력 cert: false bind-addr: 해당 서비스 주소 auth: 패스워드 설정 여부 password: 패스워드 지정 cert: 인증서 설정 bind-addr값을 127.0.0.1에서 0.0.0.0으로 변경하는 것은 외부에서 접속하는 것도 허용함을 의미한다.\nsudo systemctl restart code-server@$USER 모든 설정이 완료되었다면 code-server를 재실행해준다. 이제 브라우저에서 ip-address:8080으로 접속할 수 있게 되었다. 포트 번호를 넣지 않고 주소만으로 접속하고 싶다면 아래도 이어서 따라와주길 바란다.\nNginx 구축 Nginx는 Apache와 같은 웹 서버 소프트웨어이다. 아파치가 이전에 많이 사용되었다면 최근 만들어지는 프로젝트의 다수는 Nginx로 구성하는 추세이다. 가벼우면서도 아차피보다 더 많은 프로세스를 감당할 수 있다는 장점도 있다.\n이 Nginx는 포워드 프록시라는 기능을 제공하는데 이를 활용하면 서버 ip주소만으로 서비스 포트가 8080인 code-server를 접속할 수 있다.\nbind-addr 초기화 vi ~/.config/code-server/config.yaml # bind-addr: 127.0.0.1:8080 sudo systemctl restart code-server@$USER 해당 과정을 위해서는 code-server의 bind-addr를 localhost로 다시 설정해주어야 한다.\nNginx 설치 sudo apt install -y nginx /etc/nginx/sites-available/code-server 파일에 아래 설정 추가(sudo 권한) server { listen 80; listen [::]:80; server_name mydomain.com; # 도메인이 있다면 변경 location / { proxy_pass http://localhost:8080/; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Accept-Encoding gzip; } } 80번 포트로 listen하고 해당 프록시를 localhost의 8080 포트로 pass하는 설정이다.\n설정 적용 sudo rm /etc/nginx/sites-enabled/default sudo ln -s ../sites-available/code-server /etc/nginx/sites-enabled/code-server sudo systemctl reload nginx.service 설정이 제대로 적용되었다면 이후부터는 ip주소만으로 브라우저에서 code-server을 이용할 수 있게 된다.\n버전 업데이트 이 포스팅을 쓰는 도중에도 코드 서버는 v3.7.3에서 v3.7.4로 버전 업데이트가 이루어졌다. 몇 가지 버그 픽스와 기능 추가가 있는 작은 업데이트이었지만 code-server는 접속할 때마다 새로운 버전이 나왔음을 알려주었다. 버전을 업데이트하는 방법은 매우 간단하다.\ncurl -fsSL https://code-server.dev/install.sh | sh 설치할 때 사용한 코드를 동일하게 실행해주면 바로 적용된다. 이렇게 지속적으로 빠르게 업데이트가 되는 것은 이 프로젝트가 오픈 소스이기 때문에 얻을 수 있는 큰 장점이다. 이렇게 좋은 개발환경을 무료로 자유로이 사용할 수 있다는 것에 감사하고 있다.\n추가적인 설정 이렇게 해서 개인 서버에 code-server를 설치해서 웹 브라우저를 통해 접속할 수 있는 환경을 만들어 보았다. 공식 문서에서는 추가적으로 Nginx 등을 활용하여 https 통신으로 암호화하는 것을 권장하는데, 자세한 내용은 여기를 통해 볼 수 있다. 해당 방법은 도메인네임이 있어야만 하므로 해당 비용을 감수하기 싫다면 굳이 진행하지 않아도 무관하다.\n","permalink":"https://vanslog.io/ko/posts/infra/build-web-ide-using-code-server/","summary":"나만의 웹 IDE 만들기","title":"code-server를 이용해서 웹 IDE 구축하기"},{"content":"서론 최근 아니 이미 몇 년간 이어져온 트렌드는 Cloud이다. 어디서나 접속 가능한 컴퓨팅 자원 및 서비스를 제공하는 것인데, 이것이 가져오는 편의성은 어마어마하다. 우리는 더 이상 내가 가진 자원에 종속되지 않아도 됨을 의미하고, 어디에 있든 언제든지 간에 클라우드 자원에 접속하여 이전 작업을 이어갈 수 있다.\n실제로 클라우드의 수혜를 보는 것은 서비스를 사용하는 사용자보다도 개발자이다. 사용자 입장에서는 클라우드 서비스니 뭐니 해도 레거시한 서버와 차이를 느끼기 힘든 반면, 개발자 입장에서는 클라우드를 도입해서 서버 관리에 이점이 확실하기 때문이다.\n개발자에게 클라우드가 이로운 점이 이뿐만이 아니다. 개발서버를 구축하는 것은 개발환경에 있어서의 종속성을 벗어날 수 있게 해준다. 로컬에서 작업한 작업물은 다른 PC에서 이어서 작업하기 위해서 준비해야 할 것들이 사라진다. 물론 git으로 프로젝트를 관리한다고 하는 방법으로 해결할 수 있지 않느냐라는 질문이 있을 수 도 있겠다. 하지만 종속적인 환경들을 다른 PC에서 매번 준비한다는 것은 불편하다.\n특히나 나와 같이 군대에서 코딩하려는 사람에게는 클라우드 환경 만큼이나 유용한 것은 없을 것이다.\n대표적인 클라우드 제공업체로는 AWS(Amazon Web Service), MS Azure, GCP(Google Cloud Platform)이 있다. AWS는 1년간 매달 750시간 사용 가능한 리눅스 인스턴스를 제공하며, 애저 또한 무료 크레딧을 제공한다. 하지만 오늘 소개할 것은 평생 무료 인스턴스를 제공하는 GCP이다.\nGCP가 신규 회원에게 제공하는 혜택은 아래와 같다.\n- 평생 무료 이용 가능한 인스턴스 - 3달 동안 이용 가능한 $300 크레딧 인스턴스 생성 인스턴스 생성에 앞서 구글 클라우드 플랫폼(GCP)에 가입하자. 가입하는 과정은 그리 어렵지 않으니 따로 설명하진 않겠다. 회원가입이 되었다면 우선 아무런 이름이든 상관없으니 프로젝트를 생성한다. 이를 위해서 콘솔로 접속해서 진행한다.\n앞에서의 과정이 모두 끝났다면 인스턴스를 생성해준다. 생성한 프로젝트에서 햄버거 메뉴를 눌러 Compute Engine-VM 인스턴스를 클릭해서 들어가주고, 위 사진에 보이는 플러스 버튼을 눌러 인스턴스 생성을 시작하자.\n여기서 이름은 원하는 대로 지어주면 되고, 중요한 부분은 리전과 머신 구성 부분이다. 평생 무료 인스턴스를 만들기 위해서는 조건이 있는데, 우선 region을 us-east1-b를 사용해야 하고 머신 유형은 f1-micro를 사용해야 한다.\n만약 제공되는 $300 크레딧 사용이 목적이라면 다른 리전과 머신 유형으로 구성하는 것을 추천한다. 평생 무료 인스턴스의 성능이 매우 낮기 때문이다.\n이제 설정할 것은 부팅 디스크와 방화벽 설정뿐이다. 웹 서버로 이용할 서버를 구성하고 싶다면 HTTP, HTTPS를 허용을 해주어야만 한다.\n부팅 디스크 설정의 변경 버튼을 눌러서 다른 OS를 선택할 수 있는데 나는 우분투를 선호해서 Ububtu 20.04 LTS를 선택했다. 참고로 디스크는 30GB까지가 무료로 제공되는 용량이다.\n이렇게까지 설정해서 만들기를 누르면 인스턴스가 생성되며, GCP에서 지원하는 web SSH 연결을 이용해서 쉽게 접속해서 이용할 수가 있다.\n","permalink":"https://vanslog.io/ko/posts/infra/create-gcp-instance/","summary":"나만의 평생 무료 인스턴스 만들기","title":"GCP 인스턴스 만들기"},{"content":"대한민국 국적의 신체 건강한 남성이라면 누구나 군대라는 곳을 원치 않더라도 와야만 한다. 사회에서 떨어진 곳에서 자신이 하던 일을 지속하지 못한다는 점은 개개인들에게 경력단절이라는 큰 손해로 이어진다. 특히 개발자라는 직업은 특성상 빠르게 변화하는 트랜드에 민감하게 반응하고 공부해야 하는 만큼, 이로 인한 좌절은 더더욱 클 수밖에 없다.\n군대에 오고 얼마 지나지 않았을 무렵에는 사지방 PC에 직접 개발환경을 설치하고 개발해보려고 했으나 아래와 같은 제약조건에 인해 이 방법은 몇 번 사용하다가 포기했다.\n- 육군 지침에서 `자료 통신 프로토콜(ftp, telnet)`을 금하고 있다. - 이는 오픈소스 개발에 필수적인 `git`도 엄연히 사용해선 안된다는 것을 의미한다. - 사지방 PC는 자동 로그오프 기능과 초기화로 인해 개발환경 유지가 안된다. 위 제약사항을 다시보자. http나 https 프로토콜으로 웹 사이트 접속하는 것에는 전혀 문제가 없다는 것을 알 수 있다. 그렇기 때문에 웹 브라우저를 통해 통신하는 것은 지침에 위반되지 않는다. 나는 여기에 해답이 있다고 생각했고, 군대에서 코딩을 하는 방법을 찾아냈다.\n웹 기반 개발환경을 구축하면 초기화되지 않는 개인 특화된 개발환경을 사용 가능하다!\n구글링을 통해 알아본 바로 군복무를 하는 동안 cloud9이나 구름ide 등을 이용한 개발자 분들이 몇몇 계셨다. 하지만 나는 Google Cloud Platform을 활용하여 개발 환경을 구축하였다. GCP는 기본 인스턴스에 한해서는 평생 무료로 제공하며, 3달 동안 사용 가능한 $300 크레딧을 제공한다. 최소 3개월 동안은 intel의 쿼드코어 CPU, 15GB의 램 메모리로 구성된 인스턴스를 계속 운용할 수 있는 크레딧이다. 웹/앱 개발에 있어서는 충분한 성능을 제공하는 서버를 구축할 수 있다.\n이전에는 1년 간 사용가능한 $300 크레딧을 제공했으나 사용기간이 단축되었다.\n앞으로 포스팅을 통해서 서버를 생성해 개발환경을 구축하는 방법을 소개하고자 한다. 준비해야 할 단계는 딱 두 개뿐이다. 서버를 생성하고 개발환경을 구성한다.\n1편 GCP 인스턴스 만들기 2편 Code-Server로 웹 IDE 구축하기 서버가 꼭 GCP로 구성되어야 하는 것은 아니다. 개인 서버를 이용해도 좋고 다른 서비스를 이용해도 좋으나, 나는 이번에 GCP를 활용해보았고 그 방법을 정리해보게 되었다.\n","permalink":"https://vanslog.io/ko/posts/infra/coding-in-the-military/","summary":"군대에서 코딩을 어떻게 할 수 있을까? 그 답을 찾기 위한 여정","title":"군대에서 코딩하기"}]