토비의 스프링 | 8장 스프링이란 무엇인가? (학습)

주싱·2022년 11월 13일
0

토비의 스프링

목록 보기
28/30
post-custom-banner

토비의 스프링 8장 ‘스프링이란 무엇인가?’를 읽고 배우고 학습한 내용을 정리합니다.

1. 스프링의 정의

“자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크”

애플리케이션 프레임워크

  • 프레임워크는 애플리케이션의 특정 계층에서 주로 동작하는 한 가지 기술 분야에 집중된다. 하지만 스프링은 이와 다르게 ‘애플리케이션 프레임워크’라는 특징을 갖고 있다. 애플리케이션 프레임워크는 애플리케이션 개발의 전 과정을 빠르고 편리하며 효율적으로 진행하는데 일차적인 목표를 두는 프레임워크다.
  • 로드 존슨이 2003년에 쓴 Expert One-on-One J2EE Design and Develpment’라는 책을 출간했다. 이 책의 중요한 전략의 하나는 “항상 프레임워크 기반으로 접근하라”는 것이었다. 당연히 이 책의 예제 애플리케이션도 프레임워크를 먼저 만들고 나서, 프레임워크를 이용하는 코드를 만드는 방식으로 작성됐다. 바로 이 예제에 포함된 프레임워크가 스프링 프레임워크의 기원이다.
  • 스프링은 애플리케이션 전 영역을 관통하는 일관된 프로그래밍 모델과 핵심 기술을 바탕으로 해서 각 분야의 특성에 맞는 필요를 채워주고 있기 때문에, 애플리케이션을 빠르고 효과적으로 개발할 수가 있다.
  • 스프링의 일차적인 존재 목적은 핵심 기술에 담긴 프로그래밍 모델을 일관되게 적용해서 엔터프라이즈 애플리케이션 전 계층과 전 영역에 전략과 기능을 제공해줌으로써 애플리케이션을 편리하게 개발하게 해주는 애플리케이션 프레임워크로 사용되는 것임을 기억해두자.

경량급

  • 스프링은 20여 개의 모듈로 세분화되고 수십만 라인에 달하는 코드를 가진 매우 복잡하고 방대한 규모의 프레임워크다. 그럼에도 스프링이 가볍다고 하는 이유는 무엇일까? 그것은 불필요하게 무겁지 않다는 의미다.
  • 스프링 위에서 만들어진 코드가 지원하는 기술수준은 비슷하더라도 그것을 훨씬 빠르고 간편하게 작성하게 해줌으로써 생산성과 품질 면에서 유리하다는 것이 바로 경량급이라는 말로 표현되는 스프링의 특징이다.

자바 엔터프라이즈 개발을 편하게

  • 애플리케이션 개발이란 개발자가 복잡하고 실수하기 쉬운 로우레벨 기술에 많은 신경을 쓰지 않으면서도 애플리케이션의 핵심인 사용자의 요구사항, 즉 비지니스 로직을 빠르고 효과적으로 구현하는 것을 말한다.
  • 스프링은 애플리케이션 개발자들이 스프링이라는 프레임워크가 제공하는 기술이 아니라 자신이 작성하는 애플리케이션의 로직에 더 많은 관심과 시간을 쏟게 해준다.
  • 스프링은 EJB와 동일하게 ‘애플리케이션 작성을 편하게 해준다’라는 목적을 추구하지만 그 과정에서 다른 불편함을 추가하지 않아도 되게 해준다.

오픈소스

  • 스프링은 아파치 라이선스 버전 2.0을 사용한다.
  • 스프링은 SpringSource 라는 회사에서 그 개발을 전적으로 책임지고 전담하고 있다.
  • 스프링은 모든 개발 과정이 공개되어 있지만 실제 개발에 참여할 수 있는 개발자들은 제한되어 있다.
  • SpringSource는 2009년에 VMWare에 합병되었다.

2. 스프링의 목적

2.1 들어가며

  • 자바를 가져다가 절차지향 언어처럼 사용한다면 자바를 사용하는 가치를 얻을 수 없다.
  • ⭐⭐ 어떤 기술이든 그 자체로는 도구에 불과하다. 그것을 용도에 맞게 잘 활용해서 궁긍적으로 이루고자 하는 목표를 이루는 것이 중요하지, 도구의 사용법만 열심히 익힌다고 결과를 저절로 얻을 수 있는 건 아니다.
  • 스프링을 사용해서 엔터프라이즈 애플리케이션 개발을편하게 하려는 이유는 무엇일까? 원래 엔터프라이즈 개발이란 편하지 않기 때문이다.

2.2 엔터프라이즈 개발의 복잡함

※ 엔터프라이즈 시스템이란 서버에서 동작하며 기업과 조직의 업무를 처리해주는 시스템을 말한다.

복잡함의 근본적인 이유

  • 기술적인 제약조건과 요구사항이 늘어가기 때문이다.
  • 애플리케이션이 구현해야 할 핵심기능인 비지니스 로직의 복잡함이 증가하기 때문이다.

복잡함을 가중시키는 원인

  • 복잡하다는 건 단지 양이 많고 어렵다는 뜻이 아니다. 세부 요소가 이해하기 힘든 방식으로 얽혀 있고, 그 때문에 쉽게 다루기 어렵다는 의미이다. 자칫 잘못 손을 댔다가는 더 엉망이 되기 쉬우며, 들인 노력과 시간이 허사가 될 수도 있다.
  • 자바 엔터프라이즈 시스템 개발의 어려운 가장 큰 이유는 ⭐⭐근본적인 비즈니스 로직과 엔터프라이즈 기술이라는 두 가지 복잡함이 한데 얽혀 있기 때문이다.
  • 일반적으로 사람은 성격이 다른 두 가지 종류의 일을 동시에 생각하고 처리하는데 매우 취약하다.

2.3 복잡함을 해결하려는 도전

제거될 수 없는 근본적인 복잡함

  • 엔터프라이즈 개발의 근본적인 복잡함의 원인은 제거할 대상은 아니다. 대신 그 복잡함을 효과적으로 상대할 수 있는 전략과 기법이 필요하다. 문제는 비지니스 로직의 복잡함을 효과적으로 다루기 위한 방법과 기술적인 복잡함을 효과적으로 처리하는 데 적용되는 방법이 다르다는 점이다. 따라서 두 가지 복잡함이 코드에 한데 어우러져 나타나는 전통적인 개발 방식에서는 효과적으로 복잡함을 다루기가 힘들다. 따라서 ⭐⭐가장 먼저 할 일은 성격이 다른 이 두 가지 복잡함을 분리해내는 것이다.

실패한 해결책: EJB

  • EJB 역시 개발자가 로우레벨의 기술적인 복잡함에 신경쓰지 않고 비즈니스 로직을 효과적으로 개발하는 데 더 집중할 수 있게 하자는 목표가 있었다.
  • 그러나 애플리케이션 로직을 담은 핵심 코드에서 일부 기술적인 코드가 제거된 건 사실이지만, 오히려 EJB라는 환경과 스펙에 종속되는 코드로 만들어져야 하는 더 큰 부담을 안게 됐다.
  • EJB는 결국 일부 기술적인 복잡함을 덜어주려는 싣도를 하다가 오히려 더 큰 복잡함을 추가하는 실수를 범했다.⭐⭐
  • EJB는 결국 객체지향적인 특성은 잃어버린 밋밋한 서비스 스크립트성 코드로 변질돼갔다.
  • 게다가 EJB의 발전주기는 너무 느려서 엔터프라이즈 개발 기술의 발전을 따라잡지 못하는 문제도 있었다.

비침투적인 방식을 통한 효과적인 해결책: 스프링

  • 스프링은 EJB와 마찬가지로 기술적인 복잡함을 애플리케이션 핵심 로직에서 제거하는 데 목표를 뒀다.
  • EJB처럼 어떤 기술을 적용했을 때 그 기술과 관련된 코드나 규약 등이 코드에 등장하는 경우를 침투적인 기술이라고 한다. 반면에 비침투적인 기술은 기술의 적용 사실이 코드에 직접 반영되지 않는 특징이 있다. 물론 기술의 적용에 따라 필요한 작업을 해줘야 하겠지만, 애플리케이션 코드 여기저기에 불쑥 등장하거나, 코드의 설계와 구현 방식을 제헌하지는 않는다는 게 비침투적인 기술의 특징이다.
  • 스프링 스스로가 애플리케이션 코드에 불필요하게 나타나지 않도록 하는 것이다. ⭐⭐ 꼭 필요할 것 같은 경우조차도 기술 코드가 직접 노출되지 않도록 만들어줬다.

2.4 복잠함을 상대하는 스프링의 전략

스프링의 기본적인 전략은 ⭐⭐ 비즈니스 로직을 담은 애플리케이션 코드와 엔터프라이즈 기술을 처리하는 코드를 분리시키는 것이다.

기술적 복잡함을 상대하는 전략

  • 첫 번째 문제: 기술에 대한 접근 방식이 일관성이 없고, 특정 환경에 종속적이다.
    • 일관성 없는 기술과 서버환경의 변화에 대한 스프링의 공략 방법은 바로 서비스 추상화이다.
    • 기술적인 복잡함은 일단 추상화를 통해 로우레벨의 기술 구현 부분과 기술을 사용하는 인터페이스를 분리하고, 환경과 세부 기술에 독립적인 접근 인터페이스를 제공하는 것이 가장 좋은 해결책이다.
  • 두 번재 문제: 기술적인 처리를 담당하는 코드가 성격이 다른 코드에 섞여 등장한다.
    • 기술과 비즈니스 로직의 혼재로 발생하는 복잡함을 해결하기 위한 스프링의 접근 방법은 바로 AOP다
    • AOP는 최후까지 애플리케이션 로직을 담당하는 코드에 남아 있는 기술 관련 코드를 깔끔하게 분리해서 별도의 모듈로 관리하게 해주는 강력한 기술이다. AOP를 적용하지 않았을 때는 기술과 비지니스 로직이 지저분하게 얽혀서 다루기 힘들다는 문제도 있지만, 기술적인 코드가 여기저기 중복돼서 나타난다는 것도 심각한 문제점이다. 이 때문에 기술적인 작업을 처리하는 방식이 변경될 경우 많은 곳을 수정해야 한다. AOP는 기술을 다루는 코드로 인한 복잡함이 기술 그 자체 이상으로 불필요하게 증대되지 않도록 도와주는 가장 강력한 수단이다.

비즈니스와 애플리케이션 로직의 복잡함을 상대하는 전략

  • 기술적인 오류는 시스템을 복구하거나 빠르게 대응해주면 당장에 큰 문제가 발생하지는 않는다. 그러나 비즈니스 로직을 다루는 핵심 코드에 오류가 있으면 치명적인 문제가 발생할 수 있다. (증권사 사용자 급증으로 서비스가 느려지거나 일시적으로 다운되는 것 vs 주식 거래 체결에 오류가 생기는 것)
  • 스프링은 다만 뒤에서 비즈니스 로직을 담당하는 객체들에게 적절한 엔터프라이즈 기술 서비스가 제공되도록 은밀히 도울 뿐이다.
  • ⭐⭐ 결국 비즈니스 로직의 복잡함을 상대하는 전략은 자바라는 객체지향 기술 그 자체다. 스프링은 단지 객체지향 언어의 장점을 제대로 살리지 못하게 방해했던 요소를 제거하도록 도와줄 뿐이다.

핵심도구: 객체지향과 DI

  • 스프링의 모토는 결국 “기본으로 돌아가자”이다. 자바의 기본인 객체지향에 충실한 설계가 가능하도록 단순한 오브젝트로 개발할 수 있고, 객체지향의 설계 기법을 잘 적용할 수 있는 구조를 만들기 위해 DI 같은 유용한 기술을 편하게 적용하도록 도와주는 것이 스프링의 기본 전략이다.
  • 결국 모든 스프링의 기술과 전략은 객체지향이라는 자바 언어가 가진 강력한 도구를 극대화해서 사용할 수 있도록 돕는 것이라고 볼 수 있다. ⭐⭐스프링은 단지 거들 뿐이다.
  • 스프링만 잘 공부하면 자바 언어 자체나 객체지향 설계와 개발 실력 따윈 별로 신경 쓰지 않아도 복잡한 엔터프라이즈 시스템 개발을 잘할 수 있을 거라고 생각하면 오산이다.

3. POJO 프로그래밍

  • ⭐⭐ “스프링의 정수(essence)는 엔터프라이즈 서비스 기능을 POJO에 제공하는 것” (스프링 핵심 개발자들이 함께 쓴, 책 Professional Spring Framework 중)
  • ⭐⭐ 분리됐지만 반드시 필요한 엔터프라이즈 서비스 기술을 POJO 방식으로 개발된 애플리케이션 핵심 로직을 담은 코드에 제공한다는 것이 스프링의 가장 강력한 특징과 목표다.

3.1 스프링의 핵심: POJO

  • 스프링 애플리케이션은 POJO를 이용해서 만든 애플리케이션 코드와, POJO가 어떻게 관계를 맺고 동작하는지를 정의해놓은 설계정보로 구분된다. 스프링의 주요 기술인 IoC/DI, AOP, PSA(Portable Service Abstraction)는 애플리케이션을 POJO로 개발할 수 있게 하는 가능기술(enabling technology)이라고 불린다.

3.2 POJO란 무엇인가?

  • POJO (Plain Old Java Object)
  • 자바의 단순한 오브젝트를 이용해 애플리케이션의 비즈니스 로직을 구현하는 것

3.3 POJO의 조건

  • 특정 규약에 종속되지 않는다. POJO는 자바 언어와 꼭 필요한 API 외에는 종속되지 않아야 한다. 특정 규약을 따라 비즈니스 컴포넌트를 만들어야 하는 경우는 POJO가 아니다. 객체지향 설계의 자유로운 적용이 가능한 오브젝트여야만 POJO라고 불릴 수 있다.
  • 특정 환경에 종속되지 않는다. 특정 환경에 종속적이어야만 동작하는 오브젝트도 POJO라고 할 수 없다.
  • POJO는 객체지향적인 자바 언어의 기본에 충실하게 만들어져야 한다. 책임과 역할이 각기 다른 코드를 한 클래스에 몰아넣어 덩치 큰 만능 클래스로 만드는 경우, 재사용이 불가능할 정도로 다른 레이어와 영역의 코드와 강한 결합을 가지고 만들어지는 경우, 상속과 다형성의 적용으로 처리하면 깔끔한 것을 if/switch 문이 가득 찬 길고 긴 메서드로 작성해놓은 경우라면 과연 그것이 객체지향적인 자바오브젝트라고 할 수 있을지 의문이다.

3.4 POJO의 장점

  • 특정한 기술과 환경에 종속되지 않는 오브젝트는 그만큼 깔끔한 코드가 될 수 있다. 로우레벨의 기술과 환경에 종속적인 코드가 비즈니스 로직과 함께 섞여 나오는 것만큼 지저분하고 복잡한 코드도 없다. 그런 코드는 개발하기도 힘들고, 오류를 찾고 디버깅하기는 더더욱 힘들다. 코드를 읽고 이해하기도 어려울뿐더러 검증이나 테스트 작성에도 한계가 있으므로 유지보수는 큰 부담이 된다.
  • 객체지향적인 설계를 자유롭게 적용할 수 있다는 것도 큰 장점이다. 자바 언어의 객체지향적인 설계와 구현 방식이야말로 그 어떤 새로운 기술과 환경, 툴보다 더 실제 프로젝트를 성공시키는 데 중요한 요소임을 알고 있기 때문이다.

3.5 POJO 프레임워크

  • POJO 프로그래밍이 가능하도록 기술적인 기반을 제공하는 프레임워크를 POJO 프레임워크라고 한다.
  • 스프링은 엔터프라이즈 애플리케이션 개발의 모든 영역과 계층에서 POJO 방식의 구현이 가능하게 하려는 목적으로 만들어졌다.
  • 스프링은 비즈니스 로직의 복잡함과 엔터프라이즈 기술의 복잡함을 분리해서 구성할 수 있게 도와준다. 하지만 ⭐⭐ 자신은 기술영역에만 관여하지 비즈니스 로직을 담당하는 POJO에서는 모습을 감춘다.
  • 스프링을 사용하더라도 객체지향 분석과 설계에 대한 지식을 습득하고 훈련해야 한다. 당연히 자바 언어와 JVM 플랫폼 그리고 JDK API의 사용법도 잘 알아야 한다. 디자인 패턴과 구현 패턴, 좀 더 나은 코드 구조를 만들기 위한 리팩토링 기술 또한 필요하다. ⭐⭐ 스프링을 사용한다고 해서 이런 부담이 줄어드는 건 아니다. 대신 스프링은 개발자들이 복잡한 엔터프라이즈 기술보다는 이러한 객체지향적인 설계와 개발의 원리에 좀 더 집중할 수 있도록 기회를 준다.
  • 동시에 스프링이 제공하는 기술과 프레임워크 API 및 확장 포이트는 그것을 이용하는 코드가 자연스럽게 객체지향적인 설계원리를 따라가도록 이끌어주기도 한다.

4. 스프링의 기술

  • 스프링에서는 POJO 프로그래밍을 손쉽게 할 수 있도록 지원하는 세 가지(IoC/DI, AOP, PSA) 가능기술(enabling technology)을 제공한다.
  • 이 세 가지 모두 스프링이 있기 이전에도 여러 가지 형태로 시도됐고 발전하고 있었던 기술이지만 스프링은 그것을 통일성 있게, 더 세련된 방법으로, 자바 엔터프라이즈 개발의 전 영역에 걸쳐 효과적으로 적용될 수 있도록 프레임워크 형태로 제공하고 있다.

4.1 제어의 역전(IoC)/의존관계 주입(DI)

일반

  • 왜 두 개의 오브젝트를 분리해서 만들고, 인터페이스를 두고 느슨하게 연결한 뒤, 실제 사용할 대상은 DI를 통해 외부에서 지정하는 것일까? 이렇게 DI 방식으로 하는 것이 그렇지 않은 경우, 즉 직접 자신이 사용할 오브젝트를 new 키워드로 생성해서 사용하는 강한 결합을 쓰는 방법보다 나은 점은 무엇일까?
  • 가장 간단한 답변은 ‘유연한 확장이 가능하게 하기 위해서’라고 할 수 있다. DI는 개방폐쇄원칙이라는 객체지향 설계 원칙으로 잘 설명될 수 있다. 유연한 확장이라는 장점은 OCP의 ‘확장에는 열러 있다’에 해당한다. DI는 역시 OCP의 ‘변경에는 닫혀 있다’라는 말로도 설명이 가능하다. 폐쇄 관점에서 볼 때 ‘재사용이 가능하다’라고 볼 수 있다.
  • A→B라는 의존관계를 갖는 오브젝트 구조라고 생각해보자. 여기서 확장은 B가 자유롭게 변경될 수 있음을 의미한다. 이는 B가 변경돼도 A는 아무런 영향을 받지 않고 그대로 유지 가능하다는 뜻이기도 하다. B 관점에서는 유연한 확장이고 A 관점으로 보자면 (B가 바뀌어도) 변경 없이 재사용이 가능하다고 볼 수 있는 것이다.

DI의 활용 방법

  • 핵심기능의 변경: 디자인 패턴의 전략 패턴을 활용한다. A→B 구조에서 A의 기능 일부를 B에게 위임한다고 했을 때 B의 구현 방식을 필요에 따라 통째로 B1, B2, B3로 바꾸는 것이다.
  • 핵심기능의 동적인 변경: 핵심기능을 런타임 시에 조건에 따라 다르게 처리하는 방법도 적용할 수 있다. (고객에 따라 더 성능 좋은 DataSource를 사용한다던지)
  • 부가기능의 추가: 핵심기능은 그대로 둔 채로 부가기능을 추가하는 데코레이터 패턴을 활용하는 방법이 있다. 데코레이터 패턴을 통해 핵심기능과 클라이언트 코드에는 전혀 영향을 주지 않으면서 부가기능을 얼마든지 적용할 수 있다. 부가 기능의 추가 방식을 특정 오브젝트가 아니라 좀 더 많은 대상으로 일반화해서 적용하면 AOP가 된다.
  • 인터페이스의 변경: 사용하려는 오브젝트가 클라이언트와 호환되지 않는 경우 또는 여러 종류의 인터페이스를 가졌지만 사실은 비슷한 기능을 담당하는 오브젝트를 바꿔가면서 사용하고 싶은 경우 어댑터 패턴을 응용해 볼 수 있다.
  • 프록시: 오브젝트의 레퍼런스는 주입받아야 하지만 실제 초기화는 사용하는 시점에 하면 좋은 지연 로딩(lazy loading)과 같은 응용은 DI를 활용한 프록시 패턴을 적용할 수 있다.
  • 템플릿과 콜백: 반복적으로 등장하지만 항상 고정적인 변하지 않는 작업 흐름과 그 사이에서 자주 바뀌는 부분을 분리해서 템플릿과 콜백으로 만들고 DI를 적용해 볼 수 있다. 템플릿은 한 번 만들어두면 콜백이 바뀌어도 재사용할 수 있다는 기능의 확장에도 변하지 않는다는 OCP의 폐쇄 원칙에도 잘 들어맞는다.
  • 싱글톤과 오브젝트 스코프: DI할 오브젝트의 생명주기를 컨테이너가 관리해 주기 때문에 편리하다. 전통적인 싱글톤 패턴은 오브젝트에 많은 제약을 가해서 만들어지기 때문에 컨테이너에서 생성하고 관리하는 싱글톤 빈은 매우 유용하다.
  • 테스트: 여타 오브젝트와 협력해서 동작하는 오브젝트를 효과적으로 테스트하는 방법은 가능한 한 고립시키는 것이다. DI를 활용하면 테스트를 위한 클라이언트 코드의 변경 없이 의존 오브젝트를 의도한 대로 자유롭게 제어해서 효과적으로 테스트를 진행할 수 있다.

4.2 에스펙트 지향 프로그래밍(AOP)

일반

  • 스프링의 목적인 POJO만으로 엔터프라이즈 애플리케이션을 개발하면서도 엔터프라이즈 서비스를 ⭐⭐선언적으로 제공하는 데 반드시 필요한 것이 바로 이 AOP 기술이다.

AOP의 적용 기법

  • 스프링과 같이다이내믹 프록시를 사용하는 방법
  • AspectJ와 같이 자바 언어의 한계를 넘어 언어의 확장을 이용하는 방법

AOP의 적용단계

  • AOP는 하나의 모듈이 수많은 오브젝트에 보이지 않게 적용되기 때문에 매우 주의해서 사용해야 한다.
  • AOP 적용 1단계: 미리 준비된 AOP 이용
  • AOP 적용 2단계: 전담팀을 통한 정책 AOP 이용
    • 애플리케이션 전체적으로 적용가능한 것을 소수의 AOP 담당자 관리하에 적용해볼 수 있다. 대표적으로 비즈니스 로직을 가진 오브젝트에 대한 보안, 특정 계층의 오브젝트 이용 전후의 작업 기록을 남기는 로깅, 데이터 추적을 위한 트레이싱, 특정 구간의 실시간 성능 모니터링 같은 정책적으로 적용할 만한 기능에 AOP를 이용하는 것이다.
    • 특정 모듈을 호출하는 클라이언트를 제한한다 던지, 특정 계층에서 던질 수 있는 예외를 제한하도록 하는 목적으로도 사용될 수 있다.
    • 물론 개발이 끝나고 실전에 적용할 때는 정책 검증을 위한 AOP 설정을 간단히 제거할 수도 있다.
  • AOP 적용 3단계: AOP의 자유로운 이용

4.3 포터블 서비스 추상화(PSA)

  • 환경과 세부 기술의 변화에 관계없이 일관된 방식으로 기술에 접근할 수 있게 해주는 PSA(Portable Service Abstraction)이다.
  • 특정 환경과 기술에 종속적이지 않다는 게 ⭐⭐그런 기술을 사용하지 않는다는 뜻은 아니다. 다만 POJO 코드가 그런 기술에 직접 노출되어 만들어지지 않는다는 말이다.
  • 어떤 경우에는 AOP나 템플릿/콜백 패턴과 결합돼서 사용되기 떄문에 직접적으로 추상화된 서비스를 사용할 필요가 없을 대도 있다. (어노테이션 기반 트랜잭션을 사용하면 추상화된 트랜잭션 서비스를 직접 사용할 필요가 없다)
  • 스프링의 서비스 추상화의 개념과 장점을 잘 이해한다면 때에 따라 직접 서비스 추상화 기법을 적용할 필요도 있다.
  • ⭐⭐서비스 추상화는 단지 구체적인 기술에 종속되지 않게 하기 위해서만 사용되는 건 아니다. 테스트가 어렵게 만들어진 API나 설정을 통해 주요 기능을 외부에서 제어하게 만들고 싶을 때도 이용할 수 있다.
profile
소프트웨어 엔지니어, 일상
post-custom-banner

0개의 댓글