전세계에는 영어처럼 왼쪽에서 오른쪽으로 쓰는 언어(LTR)뿐만 아니라 아랍어, 히브리어처럼 오른쪽에서 왼쪽으로 쓰는 언어(RTL)도 많이 있습니다
웹 페이지를 다국어로 지원할 때 단순히 left, right 같은 물리적 속성만 사용하면, RTL 언어에서는 레이아웃이 의도와 다르게 표시되는 문제가 발생합니다. 왜냐하면 예를 들어 left는 문서의 방향과 무관하게 항상 왼쪽을 의미하기 때문에, RTL 문서에서도 그대로 왼쪽에 적용되어 “시작 부분”에 적용되지 않기 때문입니다
이러한 문제 때문에 RTL 지원을 위해 별도의 CSS를 작성하거나, dir="rtl"일 때 클래스를 다르게 적용하는 등의 추가 작업이 필요해지곤 합니다.
3. Tailwind에서 기존 클래스를 덮어쓰는 방법
Tailwind CSS는 유틸리티 퍼스트(utility-first) 프레임워크로, 기본적으로 물리적 속성 기반의 클래스(.ml-4는 margin-left 등)를 제공합니다. Tailwind 자체적으로는 (과거 버전 기준) 논리적 속성 전용 유틸리티를 제공하지 않았기 때문에, RTL 지원을 위해서는 플러그인이나 커스터마이징이 필요했습니다. 다행히 Tailwind v3부터는 일부 논리적 속성을 다루는 유틸리티가 도입되었는데요. 예를 들어 ms-4는 margin-inline-start: 1rem, me-4는 margin-inline-end: 1rem에 해당하여, 콘텐츠 방향에 따라 좌우 마진을 알아서 적용해줍니다
이미 tailwind를 사용하면서 기본 값들 (w, ml-1 등등..)에 익숙해졌는데 새로운 값을 배우려니 귀찮기도 하고,
혹시 크로스 브라우징 이슈가 발생한다면.. (caniuse에 따르면 ie가 아니면 거의 문제없긴 합니다) 롤백이 쉽도록
직접 커스텀 플러그인으로 기존 위치 정보(width, left 등)을 logical properties로 overwrite하는 플러그인을 구현해서 사용해봤습니다. .ml-<값> 클래스가 margin-inline-start를, .mr-<값> 클래스가 margin-inline-end를 지정하도록 유틸리티를 추가하면 됩니다.
// tailwind.config.js의 plugins 배열 안
plugins: [
function ({ addUtilities, theme, variants }) {
const spacing = theme("spacing");
const newUtilities = {};
for (const [key, value] of Object.entries(spacing)) {
newUtilities[`.ml-${key}`] = { "margin-inline-start": value };
newUtilities[`.mr-${key}`] = { "margin-inline-end": value };
}
addUtilities(newUtilities, variants("margin"));
}
]
이렇게 하면 Tailwind의 기존 .ml-1, .mr-1, .ml-2, .mr-2, ... 클래스들이 모두 물리적 margin-left/right 대신 논리적 margin-inline-start/end로 동작하도록 덮어쓰게 됩니다.
이전에 제가 쓴 "유연한 컴포넌트" 글에서는 리액트 컴포넌트를 UI, 비즈니스 로직, 그리고 Data Fetching으로 분리했었습니다. 이렇게 분리함으로써 코드의 재사용성과 유지보수성을 높이고자 했죠.
하지만 최근 들어 사용하는 프레임워크 & 라이브러리의 deprecated나 Next.js의 급격한 변화(Next.js 13에서 14, 15로 이어지는)를 보며..; 피로감과 불안감을 느끼게 되었습니다.
특히 Next.js처럼 프레임워크 & 라이브러리의 큰 변화는 기존 코드 구조에 큰 영향을 미치고, 때로는 기존에 작성한 로직을 재작성해야 하는 상황을 만들기도 합니다. 이러한 변화는 개발자로서 큰 부담으로 다가올 수밖에 없는데, 이를 어떻게 대처할 수 있을지 고민이 많아졌습니다.
그래서 클린 아키텍처를 읽게 되었는데요.
보통 백엔트 아키텍처에서 많이 사용하고, 객체 지향을 사용해서 프론트엔드 분야에서는 좀 낯선 분야였지만 읽고 많은 영감을 얻게 되었습니다.
문제를 해결하기 위한 WHAT 과 HOW
요약하자면, 어떤 비즈니스의 문제를 해결할때 추상화를 통하여
"WHAT"(문제를 어떻게 해결할지)만 생각해야지 HOW(어떤 기술을 쓰는지)는 중요하지 않고, 분리해서 생각해야한다는 것이였습니다.
이를 클린 아키텍처에서는 layered architecture와 DIP(의존성 역전)으로 해결합니다.
예를 들어, 블로그에서 자신이 쓴 비밀 글을 조회하는 상황을 생각해봅시다.
티스토리에서는 비밀 글을 자신만 조회하거나 비밀번호를 알아야 조회할 수 있습니다. (아마도..? 제가 아는 한은 그렇습니다)
그럼 가장 높은 추상화 단계로 생각하자면
유저가 로그인을 했고, 정당한 권한을 가지고 있다. (WHAT)만 판별하면 되는 문제입니다.
여기서 로그인 판별 방식을 어떻게 할지는(HOW), 세션 인증을 사용하는지, 아님 JWT를 사용하는지는 부차적인 문제고 나중에 언제든지 바뀔 수 있습니다.
그리고 권한을 판별할때도, 지금은 비밀글을 자신밖에 못본다 하더라도 나중 업데이트를 통하여
네이버 블로그처럼 서로 이웃인 친구는 비밀글을 볼 수 있도록 나중에 바뀔 수 있겠죠. 자세한 디테일은 언제든지 갈아끼우면 됩니다.
이처럼 기술과, 비즈니스 로직을 부품화해서 언제든지 갈아끼울 수 있도록 구현하는 방식을 클린 아키텍처는 소개시켜 줍니다.
class Task {
id: string;
title: string;
isCompleted: boolean;
constructor(params: { id: string; title: string; isCompleted?: boolean }) {
if (!params.id || !params.title) {
throw new EntityError({ message: "Task must have an id and a title." });
}
this.id = params.id;
this.title = params.title;
this.isCompleted = params.isCompleted ?? false;
}
/**
* Mark the task as completed
*/
toggleMark(): void {
this.isCompleted = !this.isCompleted;
}
/**
* Change the title of the task
*/
changeTitle(newTitle: string): void {
if (!newTitle) {
throw new EntityError({ message: "New title cannot be empty." });
}
this.title = newTitle;
}
}
이 레이어는 애플리케이션의 핵심이 되는 비즈니스 엔티티를 포함하고 있습니다. 이러한 엔티티는 비즈니스 규칙을 캡슐화하며 애플리케이션 레이어와는 독립적입니다.
집합 개념으로 치면 "연산이 닫혀 있다.(Closure under an operation)" 라는 개념이 생각나는데요.
닫혀있다의 사전적 의미는 특정 연산에 대해 어떤 집합이 닫혀 있다고 말할 때, 이는 그 연산을 집합의 원소들 사이에서 수행한 결과가 항상 동일한 집합의 원소로 남아 있는 성질을 의미합니다.
예를 들어, 엔티티 레이어는 애플리케이션의 다른 레이어와 독립적으로 존재하며, 엔티티 간의 상호작용이나 연산이 일어날 때 그 결과가 항상 엔티티 레이어 내에서 관리되고 유지됩니다. 즉, 엔티티 레이어 내의 비즈니스 규칙이 적용된 연산 결과는 여전히 같은 엔티티 레이어 내에서 처리되며, 외부 레이어로부터의 영향을 받지 않고 독립성을 유지합니다.
이는 수학적 집합에서 특정 연산을 수행한 결과가 항상 그 집합 내에 남아 있는 것과 유사합니다.
// 이전
import { useLoginSession, anotherHook } from '@/hooks/login'
// 이후
import { useLoginSession } from '@/entities/auth'
import { anotherHook } from '@/hooks/login'
그외에도 정규식만으로 대응하기는 어려운 케이스가 수십, 수백 케이스 있겠죠. 특히 라이브러리 migration이 골치아팠었습니다.
그럼 쓰는 예시를 코드로 한번 봅시다.
jscodeShift 코드 예시
2번 케이스가 간단하니 코드로 한번 봅시다.
export default function transformer(file, api) {
const j = api.jscodeshift
const root = j(file.source)
root
.find(j.ImportDeclaration, {
source: { value: '@/hooks/login' },
})
.forEach((path) => {
const useLoginSessionSpecifier = path.node.specifiers.find(
(specifier) => specifier.imported && specifier.imported.name === 'useLoginSession',
)
if (useLoginSessionSpecifier) {
// 경로를 '@/entities/auth'로 변경
path.node.source.value = '@/entities/auth'
// 다른 import가 있다면 분리
const otherSpecifiers = path.node.specifiers.filter((specifier) => specifier !== useLoginSessionSpecifier)
if (otherSpecifiers.length > 0) {
// 원래 import 구문에서 useLoginSession을 제거
path.node.specifiers = [useLoginSessionSpecifier]
// 새로운 import 구문 추가
const newImport = j.importDeclaration(otherSpecifiers, j.literal('@/hooks/login'))
j(path).insertAfter(newImport)
}
}
})
return root.toSource()
}
빌드 과정에서 tsconfig.json의 compilerOptions.rootDir 설정이 다른 타입스크립트 파일을 포함하기 위해서 모노레포의 최상위 레포지토리 루트(./)로 자동 확장되고, 빌드 결과물에 다른 레포지토리의 내용이 함께 번들링되는 문제가 발생한걸로 추측됐다.
이 타입 관련 문제는 typescript 자체적인 문제로 tsup, rollup등 다른 번들러를 써도 동일한 결과가 나올것이라 추측되고 실제로 tsup, rollup 동일하게 발생했다.
해결 방법
해결방법은 생각보다 간단했는데..
1. peer dependencies
이미 다른 레포지토리를 배포했다면 해당 라이브러리를 참조해서 사용하면 된다.
그래서, peerDependencies에 다른 모노레포 레포지토리 명을 적어주면 개발할때는 export에 적어놓은 타입스크립트 원본 파일을 참조하고, 빌드할때는 peerdependencies이니까 자동적으로 다른 레포지토리의 내용이 빌드 결과물에 빠지게 된다.
참고로 rollup external 등 여러 옵션을 써봐도 다른 레포지토리의 빌드 결과는 자동적으로 빠지지 않았다. 번들러 입장에서 다른 레포지토리가 정말로 배포되어 있어서 내가 빌드 안해도 되는지 알 방법이 없으니 peerDependendicies로 명시해줘야한다.
2. 빌드할때 내 폴더의 타입은 tsc로 따로 빌드함
다른 사람들이 쓴 rollup 코드들을 보면 타입은 tsc 등으로 따로 빌드하던데 왜 그런가 했더니.. 이 rootDir문제 때문이 아닐까?
build: 빌드 시스템이나 외부 의존성에 관련된 변경사항 (예: gulp, broccoli, npm의 패키지 변경 등)
chore: 기타 변경사항 (코드 수정 없이 설정을 변경하는 경우)
ci: 지속적 통합(CI) 설정과 스크립트 변경사항 (예: Travis, Circle, BrowserStack, SauceLabs의 설정 변경 등)
docs: 문서에만 영향을 미치는 변경사항
feat: 새로운 기능에 대한 커밋
fix: 버그 수정에 대한 커밋
perf: 성능을 개선하는 코드 변경사항
refactor: 버그를 수정하거나 기능을 추가하지 않는 코드 변경사항
revert: 이전 커밋을 되돌리는 작업
style: 코드 의미에 영향을 주지 않는 변경사항 (공백, 포맷팅, 누락된 세미콜론 등)
test: 테스트 추가, 리팩토링 테스트 코드, 누락된 테스트 추가 등
echo "foo: some message" # fails
echo "fix: some message" # passes
conventional commit을 사용합니다. 허용되지 않은 양식은 husky와 commitlint library가 막습니다.
모노레포는 여러 개의 프로젝트나 라이브러리를 단일 저장소에서 관리하는 방식을 말합니다. 이러한 접근 방식은 큰 프로젝트나 여러 팀이 협업할 때 많은 이점을 제공합니다. 주요 이점으로는 코드의 재사용성 증가, 의존성 관리의 단순화, 통합된 버전 관리 등이 있습니다.
turbo repo를 이용한 모노 레포 구조를 가집니다. 각 폴더는 하나의 레포지토리를 구성합니다.
- apps
ㄴ docs -> 문서용 storybook
ㄴ RadixComponents -> 사용하는 공통 컴포넌트 (현재는 사용하고 있지 않음)
ㄴ TTTable -> 테이블 구현체
- packages
ㄴ shared -> 공통 변수
ㄴ @TDS/utils -> 공통 함수
ㄴ designFiles -> designTokens, icons들 저장소
- configs
ㄴ eslint -> eslint 설정
ㄴ tsconifg -> typescript 설정
ㄴ jestconfig -> jest 설정
apps: 실제 사용자에게 제공되는 애플리케이션들이 위치합니다. 여기에는 문서를 위한 Storybook(docs), 공통 컴포넌트(RadixComponents), 테이블 구현체(TTTable) 등이 포함될 수 있습니다.
packages: 재사용 가능한 코드, 공통 함수, 디자인 토큰, 아이콘 등을 포함하는 패키지들이 위치합니다. 이러한 패키지들은 apps 내의 프로젝트들 및 배포 후 자사 프로젝트에 의해 사용될 수 있습니다.
configs: 프로젝트 전체에 걸쳐 공유되는 설정 파일들이 위치합니다. ESLint, TypeScript, Jest 등의 설정이 포함될 수 있습니다.
script 실행 방법
최상위 package.json에 정의된 스크립트는 모노레포 내의 여러 프로젝트나 패키지에 대한 작업을 조정합니다. 예를 들어, 다음과 같은 스크립트가 있을 수 있습니다:
build: 애플리케이션과 패키지를 빌드합니다.
test: 애플리케이션과 패키지의 테스트를 실행합니다.
lint: 코드 스타일과 문법 검사를 전체 프로젝트에 걸쳐 실행합니다.
start: 개발 모드에서 특정 애플리케이션을 실행합니다.
최상위 폴더에서 pnpm run build시 하위 레포지토리의 build 명령어가 있는 레포지토리는 병렬적으로 해당 명령어를 전부 실행합니다.
다른 자세한 기능들은 turbo repo docs를 참고하시기 바랍니다.
레포지토리 배포 프로세스
1. pnpm run changeset
2. 패치 버젼, summary 등 작성
3. pnpm run changeset version (version up)
4. commit
5. PR 및 리뷰
6. master branch에 반영
7. gitlab runner에서 자동 배포
1. pnpm run changeset
이 명령은 변경 사항을 추가하기 위해 Changesets CLI를 실행합니다.
2. 패치 버전, 요약 등 작성
선택한 패키지와 업데이트 유형(예: 패치)에 대한 설명을 작성합니다. 이 설명은 추후 changelog에 반영됩니다.
이 과정은 변경 사항에 대한 명확한 기록을 남기며, 팀원들이나 사용자들이 어떤 변경 사항이 있었는지 쉽게 파악할 수 있도록 합니다.
3. pnpm run changeset version (버전 업)
이 명령은 Changesets가 생성한 변경 사항 파일을 기반으로 패키지의 버전을 실제로 업데이트하고, 각 패키지의 changelog를 업데이트합니다.
변경 사항에 대한 모든 정보는 패키지별로 관리되며, 이 단계에서 자동으로 패키지 버전이 업데이트됩니다.
4. 커밋
버전 업데이트와 changelog 변경 사항을 Git 저장소에 커밋합니다. 이 커밋에는 버전이 업데이트된 패키지와 변경된 changelog 내용이 포함됩니다.
5. PR(풀 리퀘스트) 및 리뷰
변경 사항을 메인 브랜치(예: master)에 병합하기 전에, 풀 리퀘스트를 생성합니다. 이는 코드 리뷰 과정을 통해 변경 사항을 검토하고 팀 내에서 합의를 이루기 위함입니다.
팀원들이 변경 사항을 리뷰하고, 필요한 피드백을 제공합니다. 리뷰 과정을 통해 코드 품질을 유지하고, 오류를 사전에 방지할 수 있습니다.
6. master 브랜치에 반영
리뷰를 통과한 후, 풀 리퀘스트를 메인 브랜치에 병합합니다. 이는 변경 사항이 공식적으로 프로젝트에 반영되었음을 의미합니다.
7. GitLab Runner에서 자동 배포
메인 브랜치에 병합된 후, GitLab CI/CD 파이프라인이 실행됩니다. GitLab Runner는 이 파이프라인을 통해 정의된 배포 작업을 자동으로 수행합니다.
배포 과정은 테스트, 빌드, 배포 단계를 포함할 수 있으며, 이 모든 과정은 자동으로 실행됩니다. 성공적으로 배포가 완료되면, 변경된 사항이 실제 환경에 적용됩니다.
여기서 중요하게 볼 것은 exports와 publish config, private, sideEffects, name, version 입니다.
name
라이브러리의 고유한 이름을 설정합니다. 여기서는 @TDS/radix_components로, @TDS는 범위(scope)를 나타내며, 같은 범위 내에서 유니크한 이름을 가지게 합니다. npm 등의 패키지 매니저를 통해 이 이름으로 라이브러리를 찾고 설치할 수 있습니다.
version
라이브러리의 현재 버전을 나타냅니다. 세마틱 버저닝(semantic versioning) 원칙에 따라 major.minor.patch 형식으로 관리됩니다. 초기 상태인 0.0.0에서 시작하여, 라이브러리가 업데이트될 때마다 적절한 버전 업을 합니다.
sideEffects
패키지가 부수 효과(side effects)를 가지는지 여부를 웹팩(Webpack) 같은 모듈 번들러에 알립니다. 여기서는 ./src/index.scss 파일이 부수 효과를 가진다고 명시되어 있어, 이 파일을 제외하고 트리 쉐이킹(tree shaking)을 적용할 수 있습니다.
exports
패키지의 내보내기(entry points)를 정의합니다. 이는 패키지를 사용하는 소비자가 접근할 수 있는 모듈의 경로를 명시합니다. 예를 들어, require 또는 import를 통해 메인 모듈을 가져오거나, 추가적인 파일(index.scss)에 대한 접근 방법을 제공합니다.
개발자 경험 향상을 위해서 모노레포 내에서는 코드를 참조합니다. 해당 방식을 사용하지 않으면 merge 및 테스트때마다 강제로 빌드를 한번씩 실행해줘야 합니다.
publishConfig
패키지를 배포할 때 사용할 설정을 정의합니다. access 필드로 접근성을 설정하며, registry 필드는 패키지가 배포될 npm 레지스트리의 URL을 지정합니다. 또한, exports 섹션은 배포된 패키지의 내보내기 설정을 다시 정의하여, 사용자가 패키지의 빌드된(dist) 버전을 사용할 수 있게 합니다.
개발자 경험을 향상하기 위해서 모노레포 빌드시 해당 옵션을 사용하도록 합니다.
private
(이 설정은 예시에 명시되어 있지 않지만 중요합니다) 이 필드가 true로 설정되면, 패키지가 비공개로 설정되어 npm 등의 공개 레지스트리에 배포되지 않습니다. 주로 개인 프로젝트나 팀 내부에서 사용할 패키지에 적용됩니다.
configs 레포지토리들은 private로 적용합니다.
gitlab의 package registry에서 현재 배포된 파일들을 확인 가능합니다
사내에 배포시 gitlab의 package registry에서 현재 배포된 파일들을 확인 가능합니다.
describe('Circle class', function() {
describe('when area is calculated', function() {
it('it should sets the radius', function() { ... });
it('it should sets the diameter', function() { ... });
it('it should sets the circumference', function() { ... });
});
});
보통 사람들은 A를 했으면 B가 일어난다는 인과론적 사고로 생각하지만 실제로 세상은 복잡계이기 때문에 A 만으로 B가 일어난다고 말할 순 없고, A가 있으면 B가 일어날 확률이 높아질 수 있다는 확률론적 사고로 세상을 바라봐야 한다고 저자는 말한다.
이를 설명하기 위한 예시로 전쟁을 예시로 드는데, 명장은 적은 병력으로 많은 적을 격파하는 장군이 명장이 아니고
항상 이길 수 있는 싸움, 이길 확률을 높이는 싸움을 만드는 사람을 명장이라고 저자는 설명한다.
예를 들어서 오다 노부나가는 오케하자마 전투에서 2000명의 적은 병력으로 25000명의 적군중 5000명의 적 장군 본대를 우연히 찾아 기습해서 이긴적이 있었다. 25000명의 적군이 동시에 달려들었으면 오다 노부나가는 전멸했을텐데 우연히 전장에 폭우가 쳐서 적군이 일사분란하게 움직일 수 없었고, 첩자가 우연히 적의 본대를 발견했기 때문에 온 힘을 쥐어짜서 적의 지휘계통을 무력화할 수 있었다.
저자는 오다 노부나가가 이때의 운을 믿고 또 무모한 도전을 반복했으면 오다 노부나가는 패망했을것이라고 설명한다. 하지만 이후 노부나가는 신중하게 이길 수 있는 싸움만 했고, 역사에 한 획을 긋는 위대한 명장으로 남게 되었다고 설명한다.
이처럼 모든 일은 확률에 따라 일어나고 일어나지 않을 수 있다. 그래서 옛말의 진인사대천명(盡人事而待天命)이라는 말처럼 자신이 할 수 있는 일을 모두 진행했으면 그 다음은 하늘에 맡겨야 할 것 같다.
흔히 운7기3이라는 말이 있는데, 운 7을 살리기 위해서 기3를 잘 닦아놔야 할 것 같다.
사람의 뇌에는 RAS(Reticular Activating System, 망상 신경계)라는 기관이 있다.
Reticular Activating System, 그림 출처 https://integratedlistening.com/blog/meet-the-reticular-activating-system-ras/
우리 뇌는 1초에 4억 비트 이상의 정보를 처리 가능하다. 하지만 과부하를 방지하기 위해서 이중 2000비트 정도만 의식에 들어오고 나머지는 무의식적으로 처리된다.
즉, 사람은 자신이 생각하고 의식하는 정보만 선별해서 처리하려고 노력하는 식인데
예시로 중국어를 새로 공부하기 시작하면 그 전에는 신경쓰지 않았던, 지나가는 중국인의 말소리가 들리는 식이다.
그래서 저자는 긍정적인 생각을 하는 사람은 낙관적인 정보만 들려오니 이를 이루기 위해 행동할 것이라고 주장하고
반대로 부정적인 사람은 안될것이라는 자기파괴적인 정보만 취사 선택해서 들으니 될 것도 안되게 만들 것이라고 주장하고 있다.
서커스에서 코끼리를 어릴때부터 쇠사슬로 묶어놓는다면, 코끼리가 커서 쇠사슬을 간단히 파괴 가능한 힘을 가지게 되어도 탈출 시도를 하지 않는다고 한다. 이처럼 불가능한 일처럼 보여도 시도를 했으면 일단 성공할 확률이 생길텐데, 시작조차 안하면 아예 성공 확률이 0%일 것이다.
저자는 불가능해 보이는 일이라도 계속 도전하면 RAS가 성공을 위해 길을 이끌것이고, 언젠가는 성취한다는 믿음을 갖고 있다. "어떻게" 보다는 "무엇을" 원하는지 정한다면 RAS가 성공을 위해 길을 이끌 것이다.
예를 들자면, 해리포터의 저자인 J.K 롤링이 10개 정도의 출판 시도후 낙담했다면 해리포터는 탄생하지 않았을 것이고, 월트 디즈니가 299번째의 테마파크 구상에서 포기했다면 디즈니랜드는 탄생할 수 없었다고 한다.
결론적으로 내 인생은 나의 것이니, 남의 평가에 휘둘려 포기하지 말라는 메세지를 던지고 있다.
결론
다만 "도박을 해서 부자가 된다."라는 목표를 세우게 되면 어떻게 될지?
저자의 의견을 맹목적으로 수용하진 말자.
물고기가 새처럼 날고 싶다는 목표를 세운다면 날 수 있을까?
도전도 좋지만 성공 확률을 객관적으로 판단 가능하게 메타인지를 키우는 것도 중요할 것 같다.
이 책에서 취할 수 있는 것은 다음과 같다고 생각된다.
1. RAS 시스템상 긍정적으로 생각해야 인생을 즐겁게 보낼 수 있다.
2. 내 인생의 주인공은 나니까 남에게 휘둘리지 않기
3. 도전도 좋지만 성공 확률을 객관적으로 판단 가능하게 메타인지를 키우자! 실현 가능하게 능력도 키우자!
AI 기술의 발전으로 인류는 그 어느 시대보다도 정보에 가장 쉽게 접근할 수 있지만, 그 중에서 양질의 정보를 찾아내기 가장 어려운 시대에 도래했다고 생각된다. 그래서 디지털 리터러시를 키우기 위해 앞으로 퇴근 후 짬짬이 책을 한두 권씩 볼 예정..!
벌써 4일간 4권이나 읽었다.
독후감 - 부의 추월차선 후기
일단 이 책에 대해 찾아보니 불쏘시개에 비유할 정도로 논란이 많은데 많은 사람들의 인생 방식이 틀렸다고 하는 자극적인 내용과 "나는 이렇게 하니 되었다."라는 성공 스토리가 대다수라 좀 거부감이 들 수 있다고 생각된다. 책을 읽으며 얻을 건 얻고 필요 없는 부분은 버리는 식으로 생각하자.
책의 저자는 가난한 어린 시절을 보냈는데, 어느 날 람보르기니를 몰던 자수성가한 사람을 발견하고 큰 깨달음을 얻었다고 한다. 그래서 젋었을때 백만장자가 되는 방법을 연구했고, 바로 행동으로 옮겨서 큰 부를 얻었다.
이 과정에서 깨달은 부자가 되는 공식을 제시한다.
부자가 되는 공식
1. 지도 (나아가야 할 방향)
2. 차량 (자기 자신)
3. 속도 (생각을 행동으로 옮기는 추진력)
여기서 가장 중요한것은 부자까지 가는 지도이다.
사람들은 크게 3가지 방식을 이용한다고 제시한다.
1. 인도로 가는 지도
돈을 잘 벌든 말든 상관 없이 재무적 목적지가 존재하지 않는 사람으로, 하루 벌어 하루 쓰는 사람들을 뜻한다.
어떤 팝스타의 한 달 소득이 40만 달러였지만 수입이 바로 끊기자 파산하는 케이스를 예시로 들었다.
2. 서행차선으로 가는 지도
재무적 지식은 있지만 늙어서 부자가 되는 사람들을 뜻한다.
대부분의 제태크 책들은 부동산, 주식 등에 30~40년 투자해서 여유로운 은퇴생활을 즐기도록 부추기는데 저자는 늙어서 돈을 벌면 무슨 소용이 있냐고 젋은 나이에 돈을 불려야한다고 주장한다. (이 내용은 3번에 후술)
또한, 절약으로는 절대로 부자가 될 수 없다고 말하면서 지출보다는 소득을 늘려야 한다고 주장한다.
3. 추월 차선으로 가는 지도
직장은 시간을 팔아 돈을 얻는 방법이라고 소개하면서 저자는 부자는 시간으로부터 자유롭고, 돈으로부터 자유로운 사람을 뜻한다고 한다.
이를 위해 저자는 창업을 추천하는데, 내가 일하는게 아니라 나를 위해 일하는 사람을 고용하고, 내가 일을 하지 않아도 시스템이 자동으로 돈을 벌어오는 현금 흐름을 만들어야 한다고 주장한다.
예시로 저자는 웹 페이지를 만들어 자동적으로 나오는 광고 수입으로 큰 소득을 올렸고, 끝내는 다른 회사에 팔아 큰 수익을 챙겼다고 한다.
혹은 창업 외에도 발명, 개발 등 현금 흐름을 기하급수적으로 발생시키는 어려가지 방법을 제시한다.
결론
보통 재테크 책이라고 하면 2번인 서행차선 방식을 추천하고, 부동산 주식 채권 등 금융 자산으로 현금 흐름을 늘리라는 식의 추천을 하는데
이 책은 하이리스크 하이리턴인 창업을제시하다니.. 부자가 되는 방식은 쉽지 않구나 생각이 든다.
이 책에서 취할 수 있는 것은 다음과 같다고 생각된다.
- 일을 안해도 자동으로 현금 흐름이 생기는 시스템을 생성해라.
- 많은 일 => 많은 소득이 아니라 많은 일 => 더 많은 일이다.
또한, 저자가 부자가 된 방식인 코딩을 나는 운좋게도 전공으로 삼았고, 할 수 있다. 창업 이외에도 사이드 프로젝트등 여러 방법을 사용 할 수 있지 않을까?