Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Why Typescript with Clean Architecture

912 views

Published on

웹 제품을 만들면서 점점 늘어나는 규모와 방대한 지식을 담기에 부족했던 코드베이스를 개선했던 경험을 소개합니다. 결론적으로 Typescript와 Clean Architecture를 택하기 까지의 문제의식과 해결과정, 실제 코드 상의 구현까지를 다루어보았습니다

Published in: Engineering
  • Be the first to comment

Why Typescript with Clean Architecture

  1. 1. class Human { constructor(name, age) { this.name = name; this.age = age; } } class Profile extends React.Component { render() { const yujin = new Human('yujin', 24); return ( <div> <h1>{ yujin.name }</h1> <h2>{ `${yujin.age} ` }</h2> <h3>{ yujin.githubUrl }</h3> </div> ) } }
  2. 2. class Human { constructor(name, age) { this.name = name; this.age = age; } } class Profile extends React.Component { render() { const yujin = new Human('yujin', 24); return ( <div> <h1>{ yujin.name }</h1> <h2>{ `${yujin.age} ` }</h2> <h3>{ yujin.githubUrl }</h3> </div> ) } }
  3. 3. class User implements Entity { id: number; name: string; type: User.Type; constructor( id: number, name: string, type: User.Type ) { this.id = id; this.name = name; this.type = type; } }
  4. 4. Repository - Entity를 제공하기 위한 Interface - 실제 구현은 DataSource에 의존성을 가지는 Data Layer에서 이루어짐 interface UserRepositoryType extends RepositoryType { search(query: string): Observable<List<User>> }
  5. 5. class SearchUsers extends UseCase<List<User>> { private repository: UserRepositoryType; query: string; constructor(repository: UserRepositoryType) { super(); this.repository = repository; } protected build(): Observable<List<User>> { return this.repository.search(this.query); } protected validate(): boolean { return !!this.query; } } UseCase - 도메인 영역의 End-Point - 실제로 서비스를 이용하는 사용자가 하는 행동을 정의 - repository의 interface 기능을 조합하여 원하는 결과값을 하나로 돌려준다
  6. 6. interface UserRepositoryType extends RepositoryType { search(query: string): Observable<List<User>> } class UserRepository implements UserRepositoryType { private githubApi: GithubApiProvider; constructor(githubApi: GithubApiProvider) { this.githubApi = githubApi; } search(query: string): Observable<List<User>> { return this.githubApi.searchUser(query); } }
  7. 7. interface ApiProviderDependencies { github: GithubApiProvider; } interface RepositoryDependencies { user: UserRepository; } interface UseCaseDependencies { searchUsers: SearchUsers; } class Context { private githubAxiosInstance: AxiosInstance; private apiProviders: ApiProviderDependencies; private repositories: RepositoryDependencies; useCases: UseCaseDependencies; constructor(githubAxiosInstance: AxiosInstance) { this.githubAxiosInstance = githubAxiosInstance; this.apiProviders = { github: new GithubApiProvider(githubAxiosInstance) }; this.repositories = { user: new UserRepository(this.apiProviders.github) }; this.useCases = { searchUsers: new SearchUsers(this.repositories.user) } } } const axiosInstance = Axios.create({ baseURL: Config.hosts.github, timeout: Config.timeout, headers: { 'Accept': 'application/json' } }); export const Application = new Context(axiosInstance);
  8. 8. private renderUsers = (users: List<User>) => <ul> { users.map((user, i) => <li key={ `users-${i}` }> { user.name } </li> ) } </ul>; apply(Application.useCases.searchUsers, it => it.query = query) .runOnAnimateFrame() .subscribe( users => this.setState({ users, fetchState: FetchState.FETCHED }), error => this.setState({ fetchState: FetchState.ERROR }) ) .unsubscribeBy(this.subscriptionBag)
  9. 9. interface Event { id: string; name: string; } class CardCompanyEvent implements Event { id: string; name: string; } class StockFirmEvent implements Event { id: string; name: string; }
  10. 10. interface Event { id: string; name: string; } class CardCompanyEvent implements Event { id: string; name: string; } class StockFirmEvent implements Event { id: string; name: string; }

×