저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의
2015-11-28일 KSUG에서 발표한 자료 공유합니다.


Posted by 최범균 madvirus

댓글을 달아 주세요

최근에 온라인 쇼핑 시스템을 구축하는 프로젝트를 시작했다. 쇼핑 시스템 자체는 솔루션을 약간 커스터마이징해서 사용할 예정이다. 이 시스템의 사용자는 다음의 요구 사항을 갖고 있다.

  • 현재 사용중인 ERP 시스템에서 쇼핑 시스템의 판매 정보(매출 등)도 함께 보고 싶음
  • 현재 사용중인 ERP 시스템의 재고와 쇼핑 시스템의 재고를 맞추고 싶음
  • 이 ERP 시스템은 외부에서 서비스로 제공
우리가 맡은 책임 중 하나는 쇼핑 시스템과 외부 ERP 시스템 사이의 연동을 처리하는 것이다. 이 프로젝트는 이제 막 시작되었으며 현재는 쇼핑 시스템의 디자인과 요구 사항을 정리하는 과정에 있다. 아직 쇼핑 시스템의 개발자가 본격 투입 전이고, ERP 시스템과의 연계 방식을 논의한 회의를 몇 차례 가졌다.

이 시점에서 우리는 무엇을 할 수 있을까? ERP 시스템과의 연동을 위한 논의만 했을 뿐, 아직 연동을 할 수 있는 시스템은 마련되지 않았다. 또한, 쇼핑 시스템과의 연계 방식은 논의조차 시작하지 못했다. 우리가 만들어야 할 (쇼핑 시스템과 외부 ERP 시스템 간의) 중계 기능의 구현을 시작하기에는 미결정 사항들이 (많이) 존재한다. 게다가 그 결정을 바로 할 수 있는 것도 아니다.

문제는 이 프로젝트의 일정이 녹녹치 않다는 데 있다. 만약 우리가 중계 기능을 제 때에 만들어내지 못하면, 우리 때문에 전체 일정이 밀리게 된다. 그런데, 구현을 위해 필요한 결정을 지금 바로 할 수 없는 처지이다. 그렇다면, 우리는 나중에 뒤에 가서 '그 때 외부 ERP 시스템 개발자와 쇼핑 시스템 개발자가 구현에 필요한 정보를 늦게 알려줘서 어쩔 수 없었다'라고 변경해야 하나? 이런 변명이 통한다면야 좋겠지만, 뒤에 가서 일정을 맞추느라 개!고생을 하게 될 것이다.

고수준 모듈과 저수준 모듈 구별하기

자! 그럼, 우리는 뻔히 보이는 고생을 그대로 받아들여야 하나? 답은 "아니오"이다. ERP 시스템과의 연동 API가 정해지지 않았어도, 아직 쇼핑몰 시스템의 개발자와 미팅을 할 수 없어도, 우리는 중계 기능의 일부를 미리 만들어 낼 수 있다. 우리가 만들 수 있는 영역은 바로 "고수준 모듈"과 관련된 코드이다.

소프트웨어는 고수준 모듈과 저수준 모듈로 구분할 수 있다. 고수준 모듈은 의미 있는 기능을 제공하는 모듈이며, 저수준 모듈은 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능의 실제 구현이 된다. 우리가 만들어볼 중계 기능의 일부를 예로 들면 고수준 모듈과 저수준 모듈은 아래와 같이 구분할 수 있다.


여기서, 우리가 저수준 모듈에서 할 수 있는 것이라곤 결과를 보관할 DB 테이블을 정하는 정도이다. 쇼핑 DB에서 데이터를 가져오는 방법도, ERP 서비스 제공 업체에 데이터를 넘기는 방법도 우리가 마음대로 할 수 없다. 하지만, 이런 저수준의 구현 상세함이 정해지지 않았다 하더라도 우리는 고수준의 기능을 구현할 수 있다. 비밀은 바로 DIP에 있다.


DIP를 적용해서 저수준 모듈 없이 고수준 모듈 구현하기


DIP는 Dependency Inversion Principle의 약자로, 이 원칙에 대한 설명은 본 글에서는 생략한다. 이에 대한 내용이 궁금한 독자는 http://en.wikipedia.org/wiki/Dependency_inversion_principle 글 또는 필자가 지은 '객체 지향과 디자인 패턴' 책을 참고하기 바란다.


DIP를 적용해서 저수준 모듈이 고수준 모듈에 의존하게 바꾸면, 저수준 모듈의 상세한 구현 없이도 고수준 모듈을 (어느 정도 수준까지) 만들어낼 수 있다. 아래 그림은 DIP를 적용한 결과를 보여주고 있다.



OrderInfoSync가 의존하는 타입은 모두 인터페이스이므로 Mock을 이용해서 얼마든지 다양한 시나리오의 테스트 코드를 만들 수 있다. 우리는 아래쪽에 붉은 색 상자안에 위치한 저수준 모듈 클래스의 구현이 존재하는지 여부에 상관없이 OrderInfoSync 클래스를 구현할 수 있다.


실제로 지금 이 방식으로 외부 연동을 위한 고수준 모듈의 코드를 한 줄 한 줄 만들어나가고 있다. 물론, 저수준 모듈의 구현없이 고수준 모듈을 완벽하게 구현할 순 없지만, 상당한 양의 구현을 사전에 미리 만들어 낼 수가 있다. 그리고 이 핵심에는 DIP가 있다!



Posted by 최범균 madvirus

댓글을 달아 주세요

거의 모든 객체 지향 관련 서적에서 설명하는 기본 객체 지향 설계 원칙이 있는데, 객체 지향 기초의 마지막 이야기로 이들 원칙들을 살펴보도록 하자.


이전글


단일 책임 원칙(SRP)


첫 번째 원칙은 '단일 책임 원칙(SRP; Single Responsibility Principle)'이다. 이는 객체는 단 한 개의 책임(역할)만을 가져야 한다는 내용으로, 객체를 변경해야 하는 이유는 단 하나여야 한다는 원칙이다. 예를 들어, Book 클래스가 JSON으로의 변환을 처리한다고 해 보자. 이 경우 클래스는 다음과 같이 정의될 것이다.


public class Book {

    public int calculatePrice() { ... }

    public String toJson() { ... }

}


만약 Book를 XML이나 HTML로도 표현해야 한다면? 이런 식으로 변환해야 할 대상이 늘어날수록 Book 클래스의 함께 변하게 된다. 이 외에도 JSON이나 XML의 구조가 변경되더라도 Book 클래스의 코드가 변경된다. Book 클래스의 코드가 표현 방식의 추가나 변경에 따라서 함께 바뀌는 증상이 발생하는데, 그 이유는 Book 클래스가 많은 책임을 지고 있기 때문이다.


책임을 많이 질수록 클래스 내부에서 서로 다른 역할을 위한 코드들 간에 강하게 결합될 가능성이 높아진다. 예를 들어, JSON으로 변환하는 코드와 HTML로 변환하는 코드와 계산을 위한 코드 중 어딘가가 연결되어 있을 수 있고, 이런 경우 계산 공식의 변화가 JSON 변환 코드에 변화를 불러 일으킬 수도 있다.


따라서, 한 객체는 하나의 책임만 갖도록 설계해야 한다. 예를 들어, 앞서 Book 클래스의 경우 책 자체의 정보를 처리하는 Book 클래스와 JSON이나 XML로 표현을 변경해주는 JsonConverter, XMLConverter 클래스로 분리해주어야 한다.


이렇게 한 객체가 하나의 책임만을 갖도록 함으로써 각 객체를 변경시키는 이유는 한 가지로 좁혀진다. 책과 관련된 계산 변경이 필요하면 Book만 변경되며, JSON이나 XML 형식이 변경되면 JsonConverter 클래스와 XMLConverter 클래스에만 변화가 생긴다. (물론, Book의 정보 자체에 변화가 생기면 그 변화는 두 Converter에 영향을 준다.)


의존 역전 원칙(DIP)


의존 역전 원칙(Dependency Inversion Principle)은 구현 클래스가 아닌 인터페이스 타입을 사용하라는 규칙이다. 이는 이미 앞서 '객체 지향 기초 3, 유연함 http://javacan.tistory.com/entry/OO-Basic-3-Flexibility'에서 말했던 내용과 동일하다. 구현 클래스는 자주 변경될 가능성이 높기 때문에, 변화 가능성이 상대적으로 적은 추상 타입이나 메서드에 의존하면 변화에 따른 영향을 최소화할 수 있다.



예를 들어, 위 그림에서 보듯이 SomeClient 클래스는 구현 클래스인 FtpLogCollector를 사용(의존)하는 것 보다, 추상 타입인 LogCollector를 사용하는 것이 변화에 영향을 덜 받게 된다. 이 경우 FtpLogCollector 자체의 구현이 변경되거나 심지어 DBLogCollector로 구현 클래스가 바뀌더라도 SomeClient는 거의 영향을 받지 않게 된다.


개방 폐쇄 원칙(OCP)


개방 폐쇄 원칙(Open-Closed Principle)은 특정 클래스(또는 모듈 등)는 그 클래스를 수정하지 않으면서 확장(extension) 가능해야 한다라는 원칙이다. 객체 지향에서 OCP는 크게 두 가지 방법으로 구현할 수 있다.

  • 상속을 통한 구현
  • 조립을 통한 구현

상속을 통한 구현은 스프링 2 버전의 MVC 컨트롤러 구성에서 확인할 수 있다. 아래 그림은 스프링 MVC의 Controller 타입의 계층 구조이다. 이 구조를 보면 AbstractController 클래스를 수정하지 않고, 상속을 통해 기능을 확장한 것을 알 수 있다. 예를 들어, 파라미터를 커맨드 객체로 받기 위해 AbstractController 클래스를 수정하지 않고 이 클래스를 상속받은 BaseCommandController 클래스를 만들었다. 즉, 변경을 하지 않으면서(Closed for modification) 확장에는 열려(open for extension)있는 것이다. 추가로 아래 클래스들은 각각 한 가지 역할만을 갖도록 설계되어 있다. 상속을 통해 기능을 확장하는 경우에도 SRP를 잘 지키고 있다.



OCP를 실현하는 두 번째 방법은 조립(composition)이다. 조립에 대한 내용은 '객체 지향 기초 4, 상속 보단 조립 http://javacan.tistory.com/entry/OO-Basic-4-Composition-Over-Inheritance)에서 살펴봤었는데, 조립과 DIP 그리고 상속이 함께 맞물리는 방법으로 OCP를 구현하게 된다. 아래 그림은 의존 역전 원칙을 설명할 때 사용한 클래스 다이어그램인데, 이 구조를 OCP에도 그대로 사용할 수 있다.



위 그림에서 SomeClient는 로그를 수집하는 기능을 조립하는 방식으로 구현하고 있다. SomeClient를 사용하는 또 다른 코드는 SomeClient가 로그 수집을 직접 수행하는지 조립을 통해 수행하는지는 모를 것이다. SomeClient는 조립을 통해 로그 수집 기능을 구현하고 있기 때문에, 로그 수집 방법을 개선할 필요가 있을 때 SomeClient는 수정하지 않으면 기능을 확장할 수 있다.


참고자료


제가 쓴 객체 지향 입문서입니다.


http://www.aladin.co.kr/shop/wproduct.aspx?ISBN=8969090010  에서 확인하실 수 있습니다.

Posted by 최범균 madvirus

댓글을 달아 주세요