NEW
지속 가능한 프런트엔드 엔지니어링 (ebook)
22,400원
초급
도서 소개
소프트웨어 개발을 잘하고 싶다면 ‘개발’ 공부를 해야 합니다!
자바 개발자가 코틀린 같은 신생 언어를 다룰 수 있게 된다고 해서 개발을 더 잘하게 되는 것은 아닙니다. 소프트웨어 개발 능력을 키우고 싶다면 ‘개발’ 그 자체를 공부하고 기초적인 설계 원리를 이해할 수 있어야 합니다. 이 책은 객체지향, SOLID, 디자인 패턴, 테스트 같은 주요 개발 관련 이론이 실제 프로젝트에 어떻게 적용될 수 있는지 설명합니다.
★ 이 책에서 다루는 내용 ★
- 객체지향
- SOLID
- 스프링 안티 패턴
- 스프링과 DDD
- 레이어드 아키텍처
- 헥사고날 아키텍처
- 테스트가 필요한 이유
- 테스트와 설계
- 테스트 대역
- TDD와 BDD
도서 상세 이미지

저자 소개
김우근
컴퓨터 공학을 전공하고 카카오에서 백엔드 엔지니어로 근무하고 있다. 현재는 에러 및 로그를 수집하는 옵저버빌리티 플랫폼을 만드는 일에 집중하고 있다. 취미가 개발이라고 당당하게 말할 수 있을 만큼 소프트웨어 개발을 사랑하며 퇴근 후에는 그림을 그리거나 개인 프로젝트를 진행한다. 2020년에는 공개 SW 개발자 대회에서 정보통신산업진흥원장상을 수상했다.
목차
- [1부] 객체지향
- ▣ 들어가며
- 개발을 배워야 한다
- __기술 vs. 개발
- __개발 능력을 길러야 하는 이유
- __스프링과 JPA는 기술이다
- 이 책을 출간하게 된 이유
- ▣ 01장: 절차지향과 비교하기
- 1.1 책임과 역할
- 1.2 TDA 원칙
- ▣ 02장: 객체의 종류
- 2.1 VO(Value Object: 값 객체)
- __2.1.1 불변성
- __2.1.2 동등성
- __2.1.3 자가 검증
- 2.2 DTO(Data Transfer Object: 데이터 전송 객체)
- 2.3 DAO(Data Access Object: 데이터 접근 객체)
- 2.4 엔티티(Entity: 개체)
- __2.4.1 도메인 엔티티
- __2.4.2 DB 엔티티
- __2.4.3 JPA 엔티티
- __2.4.4 해석
- 2.5 객체의 다양한 종류
- ▣ 03장: 행동
- 3.1 덕 타이핑
- 3.2 행동과 구현
- 3.3 인터페이스
- 3.4 행동과 역할
- 3.5 메서드
- ▣ 04장: SOLID
- 4.1 SOLID 소개
- __4.1.1 단일 책임 원칙
- __4.1.2 개방 폐쇄 원칙
- __4.1.3 리스코프 치환 원칙
- __4.1.4 인터페이스 분리 원칙
- __4.1.5 의존성 역전 원칙
- 4.2 의존성
- __4.2.1 의존성 주입
- __4.2.2 의존성 역전
- __4.2.3 의존성 역전과 스프링
- __4.2.4 의존성이 강조되는 이유
- 4.3 SOLID와 객체지향
- 4.4 디자인 패턴
- ▣ 05장: 순환 참조
- 5.1 순환 참조의 문제점
- __5.1.1 무한 루프
- __5.1.2 시스템 복잡도
- 5.2 순환 참조를 해결하는 방법
- __5.2.1 불필요한 참조 제거
- __5.2.2 간접 참조 활용
- __5.2.3 공통 컴포넌트 분리
- __5.2.4 이벤트 기반 시스템 사용
- 5.3 양방향 매핑
- 5.4 상위 수준의 순환 참조
- [2부] 스프링과 객체지향 설계
- ▣ 06장: 안티패턴
- 6.1 스마트 UI
- 6.2 양방향 레이어드 아키텍처
- __6.2.1 레이어별 모델 구성
- __6.2.2 공통 모듈 구성
- 6.3 완화된 레이어드 아키텍처
- 6.4 트랜잭션 스크립트
- ▣ 07장: 서비스
- 7.1 Manager
- 7.2 서비스보다 도메인 모델
- 7.3 작은 기계
- 7.4 조언
- ▣ 08장: 레이어드 아키텍처
- 8.1 레이어드 아키텍처의 최소 조건
- 8.2 잘못된 레이어드 아키텍처
- __8.2.1 JPA 엔티티 우선 접근
- __8.2.2 API 엔드포인트 우선 접근
- __8.2.3 본질을 다시 생각하기
- 8.3 진화하는 아키텍처
- __8.3.1 인지 모델 변경하기
- __8.3.2 JPA와의 결합 끊기
- __8.3.3 웹 프레임워크와의 결합 끊기
- 8.4 새로운 접근법
- 8.5 빈약한 도메인
- ▣ 09장: 모듈
- 9.1 모듈성
- __9.1.1 독립성
- __9.1.2 은닉성
- 9.2 패키지 구조
- __9.2.1 계층 기반 구조
- __9.2.2 도메인 기반 구조
- __9.2.3 정리
- 9.3 패키지와 모듈
- ▣ 10장: 도메인
- 10.1 소프트웨어 개발의 시작
- 10.2 애플리케이션의 본질
- 10.3 도메인 모델과 영속성 객체
- __10.3.1 통합하기 전략
- __10.3.2 구분하기 전략
- __10.3.3 평가
- ▣ 11장: 알아두면 유용한 스프링 활용법
- 11.1 타입 기반 주입
- 11.2 자가 호출
- [03부] 테스트
- ▣ 12장: 자동 테스트
- 12.1 Regression
- 12.2 의도
- 12.3 레거시 코드
- ▣ 13장: 테스트 피라미드
- 13.1 구글의 테스트 피라미드
- 13.2 테스트 분류 기준
- 13.3 소형 테스트의 중요성
- ▣ 14장: 테스트 대역
- 14.1 Dummy
- 14.2 Stub
- 14.3 Fake
- 14.4 Mock
- __14.4.1 상태 기반 검증
- __14.4.2 행위 기반 검증
- __14.4.3 상태 기반 vs. 행위 기반
- 14.5 Spy
- 14.6 정리
- ▣ 15장: 테스트 가능성
- 15.1 테스트를 어렵게 만드는 요소
- __15.1.1 숨겨진 입력
- __15.1.2 숨겨진 출력
- 15.2 테스트가 보내는 신호
- ▣ 16장: 테스트와 설계
- 16.1 테스트와 SRP
- 16.2 테스트와 ISP
- 16.3 테스트와 OCP, DIP
- 16.4 테스트와 LSP
- ▣ 17장: 테스트와 개발 방법론
- 17.1 TDD
- 17.2 BDD
- [부록]
- A: 포트-어댑터 패턴
- B: 클린 아키텍처
- C: 소프트웨어 엔지니어
- D: 실용주의
- E: 참고 자료
정오표
-
41쪽, 페이지 하단 각주 3의 첫 번째 줄
재미있는 점은 코드
2.2의==>
재미있는 점은 코드
2.1의 -
49쪽, 페이지 상단 예제 코드의 밑에서 3번째 줄
return new AccountInfo(this.id, mileage);==>
return new AccountInfo(mileage); -
51쪽, 페이지 중간 실행 결과의 3번째 줄
System.out.println(green1 == green2); // 지금 이 코드는 false를 반환합니다.==>
System.out.println(green1 == green2); // green1과 green2는 같다고 봐야 할까요? -
51쪽, 본문 7번째 줄
이때 green1 == green2는 true를 반환해야 할까요, false를 반환해야 할까요? 두 개의 초록색 객체가 있고 두 객체가 같은 것인지 아닌지 확인하는 코드입니다. 두 객체는 같다고 봐야 할까요, 다르다고 봐야 할까요?
==>
이때 green1과 green2는 같다고 봐야 할까요, 다르다고 봐야 할까요?
-
51쪽, 본문 밑에서 4번째 줄
결과적으로 green1 == green2의 결과를 예측할 수 없게 됐습니다.
==>
결과적으로 'green1과 green2를 같다고 봐야 하나?'라는 물음에 답할 수 없게 됐습니다.
-
54쪽, 노트 항목의 두 번째 코드 부분의 3번째 줄
System.out.println(account1 == account2); // 지금 이 코드는 false를 반환합니다.==>
System.out.println(account1 == account2); // account1과 account2는 같다고 봐야 할까요? -
54쪽, 노트 항목의 본문 2번째 줄
이때 account1 == account2의 결과는 true를 반환해야 할까요, false를 반환해야 할까요?
==>
이때 account1과 account2는 같은 객체라고 봐야 할까요, 다른 객체라고 봐야 할까요?
-
64쪽, 페이지 상단 첫 번째 노트의 2번째 줄
행(row)에 해당합니다.16
==>
행(row)에 해당합니다.17
-
64쪽, 페이지 하단 두 번째 노트의 3번째 줄
- 최범균17
==>
- 최범균18
-
66쪽, 페이지 하단 노트의 3번째 줄
- 이석호19
==>
- 이석호20
20 출처: 《데이타베이스 시스템》(정익사, 2017), 34쪽
-
66쪽, 본문 밑에서 2번째 줄
유무형의 객체20를 표현하기 위한
==>
유무형의 객체21를 표현하기 위한
-
66쪽, 본문 하단의 20번 각주
20여기서 말하는 객체는==>
21여기서 말하는 객체는 -
69쪽, 본문 12번째 줄
MyBatis21 같은
==>
MyBatis22 같은
-
69쪽, 본문 하단의 21번 각주
21MyBatis는 자바 언어를 위한==>
22MyBatis는 자바 언어를 위한 -
70쪽, 페이지 상단 박스의 10번째 줄
가능할 수도 있습니다.22
==>
가능할 수도 있습니다.23
-
70쪽, 본문 하단의 22번 각주
22물론 호환성을 보장하기 위한==>
23물론 호환성을 보장하기 위한 -
71쪽, 페이지 하단 박스의 5번째 줄
JSON과 유사한 BSON23 형식으로
==>
JSON과 유사한 BSON24 형식으로
-
71쪽, 페이지 하단 박스의 6번째 줄
데이터베이스입니다.24
==>
데이터베이스입니다.25
-
71쪽, 페이지 하단 박스의 마지막 줄
용어를 사용합니다.25
==>
용어를 사용합니다.26
-
71쪽, 본문 하단의 23, 24, 25번 각주
23Binary JSON의 약자로,24https://www.mongodb.com/ko-kr25개념적으로 완벽히 같은 개념은==>
24Binary JSON의 약자로,25https://www.mongodb.com/ko-kr26개념적으로 완벽히 같은 개념은 -
122쪽, 본문 5번째 줄
hamburger모듈은restaurant모듈에 의존하지 않습니다.==>
restaurant모듈은hamburger모듈에 의존하지 않습니다. -
124쪽, 본문 3~7번째 줄을 다음 내용으로 교체
모듈이 의존하는 것은 그렇게까지 이상한 현상은 아닙니다. 하지만 상위 모듈이 하위 모듈에 의존하는 것은 이상합니다. 왜냐하면 하위 모듈의 변경에 상위 모듈이 영향을 준다는 의미가 되기 때문입니다. 그러면 더는 플러그인처럼 restaurant 모듈의 기능을 자유롭게 변경할 수 없습니다.
즉 koreandish 모듈로 교체했을 때 restaurant 모듈이 영향을 받지 않고 여전히 항상성을 유지할 수 있을까요? 그림 4.10에서 chef 모듈이 사라지면 Chef 인터페이스도 함께 사라집니다. 그러면 restaurant
==>
상위 모듈이 하위 모듈에 의존하는 것은 바람직하지 않습니다. 왜냐하면 하위 모듈의 변경이 상위 모듈에 영향을 주기 때문입니다. 즉 그림 4.10처럼 되면 restaurant 모듈은 chef 모듈이 변경될 때마다 영향받은 기능이 있는지 매번 확인해야 할 것입니다.
상상해 봅시다. 그림 4.10에서 chef 모듈을 koreandish 모듈로 교체했을 때 restaurant 모듈은 항상성을 유지할 수 있을까요? 아쉽게도 그렇지 않습니다. 그림 4.10에서 chef 모듈이 사라지면 Restaurant이 의존하고 있던 Chef 인터페이스도 함께 사라집니다. 그러므로 restaurant
-
140쪽, 본문 밑에서 2번째 줄
그럼
@JsonIdentitfyInfo애너테이션을==>
그럼
@JsonIdentityInfo애너테이션을 -
142쪽, 본문 첫 번째 줄
@JsonIdentitfyInfo애너테이션을 이용해==>
@JsonIdentityInfo애너테이션을 이용해 -
203쪽, 표 7.1의 4행 4열
ProductManager
==>
ProductService
-
204쪽, 본문 4번째 줄
도메인 서비스의 파사드(facade:
정면)처럼==>
도메인 서비스의 파사드(facade:
정문)처럼 -
211쪽, 본문 9번째 줄
- 생성자가 존재하는 것이
미관상 미관상깔끔하지 않다.
==>
- 생성자가 존재하는 것이
미관상깔끔하지 않다.
- 생성자가 존재하는 것이
-
233쪽, 그림 8.7을 다음 그림으로 교체

-
237쪽, 코드 8.8의 밑에서 3번째 줄
accountJpaRepository.save(AccountJpaEntity.from(account));==>
acountJpaRepository.save(AccountJpaEntity.from(account)); return account; -
242쪽, 코드 8.10의 밑에서 3번째 줄
accountRepository.save(account);==>
accountRepository.save(account); return account; -
246쪽, 코드 8.13의 patchProperties 메서드를 다음 코드로 교체
@PatchMapping("/{id}") public ResponseEntity<Account> patchProperties( @PathVariable long id, @RequestBody PatchAccountRequest request ) { Account updatedAccount = accountService.findById(id); if (StringUtils.isNotEmpty(request.getNickname())) { updatedAccount = accountService.updateNicknameById(id, request.getNickname()); } return ResponseEntity.ok(updatedAccount); } -
247쪽, 코드 8.14를 다음 코드로 교체
public interface AccountService { public Account findById(long id); public Account updateNicknameById(long id, String nickname); } -
247쪽, 코드 8.15를 다음 코드로 교체
@Service @RequiredArgsConstructor public class AccountServiceImpl implements AccountService { private final AccountRepository accountRepository; // findById 구현 생략 @Override @Transactional public Account updateNicknameById(long id, String nickname) { Account account = accountRepository.findById(id); account = account.withNickname(nickname); accountRepository.save(account); return account; } } -
279쪽, 코드 9.9의 9번째 줄(패키지명 변경)
business==>
application -
280쪽, 본문 밑에서 3번째 줄
com.demo.myapp.
business.repository 패키지에는==>
com.demo.myapp.
application.repository 패키지에는 -
280쪽, 본문 밑에서 첫 번째 줄
적용해
business패키지가 infrastructure 패키지에==>
적용해
application패키지가 infrastructure 패키지에 -
281쪽, 코드 9.10의 2~4번째 줄
com.demo.myapp.service.UserService.java com.demo.myapp.service.repository.UserRepository.java com.demo.myapp.controller.UserController.java==>
com.demo.myapp.application.UserService.java com.demo.myapp.application.repository.UserRepository.java com.demo.myapp.presentation.UserController.java -
284쪽, 코드 9.12의 16번째 줄(패키지명 변경)
application==>
domain -
307쪽, 코드 11.7의 밑에서 7번째 줄
chatNotificationChannel.notify(account, message);==>
pushNotificationChannel.notify(account, message); -
398쪽, 코드 15.11의 10번째 줄
return currentTimestamp;==>
return current; -
402쪽, 본문 밑에서 4번째 줄
그런니메서드 호출의 출력 결과는==>
그러니메서드 호출의 출력 결과는 -
412쪽, 본문 밑에서 2번째 줄
나아가 시스템이
우연히확장될 수 있는==>
나아가 시스템이
유연하게확장될 수 있는 -
414쪽, 코드 16.1의 밑에서 3번째 줄
User user = userRepository.getByEmail(id);==>
User user = userRepository.getByEmail(email); -
417쪽, 코드 16.2의 밑에서 3번째 줄
assertThat(user.getLastLoginTimestamp()).isEqualTo(1672498800000L);==>
assertThat(user.getLastLoginTimestamp()).isEqualTo(current); -
450쪽, 본문 15번째 줄
Red, Green, Blue 절차를 따라
==>
Red, Green, Refactor 절차를 따라
-
464쪽, 본문 7번째 줄
의존성
역전 패턴을포트-어댑터 패턴이라 볼 수 있는 두 번째 이유입니다.==>
의존성
역전을포트-어댑터 패턴이라 볼 수 있는 두 번째 이유입니다.
