Luyện Phỏng Vấn IT — 2000+ Câu Hỏi Phỏng Vấn IT Có Đáp Án 2026

Danh mục

Angular iconAngular

Angular là framework frontend full-featured dùng TypeScript để xây dựng SPA, enterprise app và hybrid-rendered web app.

AngularJS là dòng 1.x cũ, khác kiến trúc hoàn toàn và không nên gọi lẫn với Angular hiện đại. So với React/Vue, Angular opinionated hơn: có DI, Router, Forms, HttpClient, CLI, testing và build pipeline chính thức; đổi lại learning curve cao hơn nhưng team lớn có convention rõ hơn.

Component là building block chính của Angular UI.

Một component thường gồm class TypeScript chứa state/logic, template HTML chứa binding, style CSS scoped theo component và metadata trong @Component như selector, templateUrl, styleUrl, imports. Với standalone component, dependency được khai báo trực tiếp trong imports thay vì cần NgModule.

Standalone component là component tự khai báo dependencies qua imports và có thể bootstrap trực tiếp bằng bootstrapApplication.

Lợi ích: ít boilerplate NgModule, dependency graph rõ hơn, lazy loading route dễ hơn và phù hợp với Angular hiện đại. NgModule vẫn dùng được trong legacy app hoặc library cũ. Nếu cần hỏi cách bootstrap/config root app, nên tách sang câu riêng để kiểm tra API cụ thể.

Interpolation {{ value }} render text ra template.

Property binding [disabled]="isSaving()" set property DOM/component input theo expression. Event binding (click)="save()" lắng nghe event và gọi method. Quy tắc nhanh: dùng {{ }} cho text, [ ] để đẩy dữ liệu xuống view/component con, ( ) để nhận event từ view/component con.

[(ngModel)] là cú pháp banana-in-a-box kết hợp property binding và event binding: dữ liệu đi xuống input, thay đổi từ input đẩy ngược lên model.

Nó thuộc FormsModule và phù hợp với form đơn giản. Với form phức tạp, validation nhiều bước hoặc test nghiêm túc, nên dùng Reactive Forms vì model explicit hơn.

input() hoặc @Input() nhận data từ parent xuống child.

output() hoặc @Output() phát event từ child lên parent. Pattern chuẩn là one-way data flow: parent sở hữu state, child nhận input và emit event khi user tương tác. Tránh để child mutate object input trực tiếp vì dễ tạo side effect khó debug.

Directive là class gắn behavior vào DOM hoặc template.

Attribute directive thay đổi behavior/appearance của element có sẵn, ví dụ highlight hoặc permission state. Structural directive thay đổi cấu trúc DOM bằng cách thêm/xóa view. Trong Angular hiện đại, legacy ngIf, ngFor, *ngSwitch đã bị deprecate từ v20 để ưu tiên block syntax như @if, @for, @switch.

Signal là reactive value được đọc bằng cách gọi function, ví dụ count(), và được cập nhật bằng set() hoặc update().

Khi signal được đọc trong template hoặc computed, Angular track dependency đó; khi value đổi, phần phụ thuộc được cập nhật. Signal giúp state local rõ ràng hơn và giảm nhu cầu dùng RxJS cho các state đồng bộ đơn giản.

Dependency Injection là cơ chế để class nhận dependency từ Angular injector thay vì tự new dependency đó.

Component/service chỉ khai báo cần gì, còn provider quyết định instance nào được cấp. Lợi ích: code dễ test, dễ mock, giảm coupling và quản lý singleton/shared services nhất quán.

Angular Router map URL sang component thông qua route config.

App standalone thường dùng provideRouter(routes), template đặt <router-outlet /> làm nơi render route hiện tại, và navigation dùng routerLink hoặc Router.navigate(). Route có thể chứa path params, query params, redirects, nested routes, lazy loading, guards và resolvers.

Template-driven Forms khai báo nhiều logic trong template qua ngModel, dễ dùng cho form nhỏ.

Reactive Forms tạo form model rõ ràng trong TypeScript bằng FormControl, FormGroup, FormArray, phù hợp form phức tạp, dynamic fields, validation nhiều tầng và unit test. Rule thực tế: form đơn giản dùng template-driven được; business form quan trọng nên dùng reactive.

HttpClient là service chính thức để gọi HTTP API, trả về Observable và hỗ trợ typed response, interceptors, params, headers, progress events và testing utilities.

Trong standalone app thường cấu hình bằng provideHttpClient(). Vì Observable của HTTP request là cold, request chỉ chạy khi được subscribe, ví dụ qua service, async pipe hoặc bridge sang signal.

TestBed tạo testing module/environment để render component, inject services và override providers trong unit test.

Nó hữu ích khi test component standalone, service có DI, provider override, pipe/directive hoặc behavior cần gần Angular runtime thật. Với logic thuần TypeScript không phụ thuộc Angular runtime, có thể test class/function trực tiếp để nhanh và ít boilerplate hơn.

Với standalone app, entrypoint thường gọi bootstrapApplication(AppComponent, appConfig).

Ví dụ cấu hình root phổ biến:

typescript
bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
    provideHttpClient(),
  ],
})

Cách này thay thế pattern cũ platformBrowserDynamic().bootstrapModule(AppModule) trong nhiều project mới, giúp root configuration nằm trong provider functions tree-shakable và dễ tách theo môi trường.

NgModule vẫn là public API và vẫn gặp nhiều trong enterprise codebase.

Vai trò chính còn lại: gom declarations/providers trong legacy modules, expose library API cũ, cấu hình forRoot/forChild, hoặc bridge với third-party package chưa standalone. Với app mới, NgModule không còn là đơn vị bắt buộc; route-level lazy loading có thể import trực tiếp standalone component hoặc route array.

Các hook hay gặp: ngOnChanges chạy khi input đổi, lần đầu chạy trước ngOnInit; ngOnInit chạy một lần sau khi input initial xong; ngAfterViewInit dùng khi view/query đã sẵn sàng; ngOnDestroy hoặc DestroyRef dùng cleanup.

Ví dụ cleanup hiện đại:

typescript
@Component({ template: "{{ userId() }}" })
export class UserPage {
  userId = input.required<string>()

  constructor() {
    inject(DestroyRef).onDestroy(() => console.log("cleanup"))
  }
}

Từ Angular hiện đại, afterNextRender/afterEveryRender phù hợp cho DOM work sau render và không chạy trong SSR/prerender.

Angular compiler phân tích template trước runtime, bind với TypeScript types và phát hiện lỗi như sai input name, sai kiểu truyền vào component, pipe không tồn tại hoặc variable template không hợp lệ.

Bật strict template checking trong tsconfig giúp bắt lỗi sớm trong CI thay vì chờ user gặp runtime bug. Với app lớn, đây là lợi thế quan trọng của Angular so với template không type-safe.

input() tạo một InputSignal đọc bằng cách gọi function, ví dụ userId(), và compose tự nhiên với computed() hoặc effect().

Ví dụ:

typescript
@Component({ template: "<p>User {{ userId() }}</p>" })
export class UserCard {
  userId = input.required<string>()
  displayId = computed(() => `#${this.userId()}`)
}

@Input() vẫn dùng được, nhất là trong code cũ; nhưng khi viết component mới, signal input giúp template và reactive state nhất quán hơn.

model() định nghĩa input có thể ghi, đồng thời tạo output <name>Change để hỗ trợ two-way binding cho custom component.

Ví dụ checkbox tự emit thay đổi:

typescript
@Component({ template: `<button (click)="checked.update(v => !v)">{{ checked() }}</button>` })
export class ToggleButton {
  checked = model(false)
}

Dùng cho component chỉnh sửa một giá trị như checkbox/date picker; nếu component chỉ render dữ liệu, dùng input() read-only rõ hơn.

Control flow block là cú pháp template built-in, không cần import directive và dễ type-check hơn.

Ví dụ:

html
@if (user(); as u) {
  <h2>{{ u.name }}</h2>
} @else {
  <app-login />
}

@for (item of items(); track item.id) {
  <app-row [item]="item" />
} @empty {
  <p>No data</p>
}

Từ Angular v20, NgIf, NgFor, NgSwitch đã deprecate, nên code mới nên dùng block syntax.

track cho Angular biết item nào là cùng một entity khi list thay đổi, giúp reuse DOM/component instance thay vì destroy/recreate sai cách.

Ví dụ đúng với dữ liệu có id:

html
@for (user of users(); track user.id) {
  <app-user-row [user]="user" />
}

Với list tĩnh có thể track item; hạn chế dùng index nếu list có sort/filter/insert.

Sai track thường gây mất focus input, reset child state hoặc animation bị giật.

@defer cho phép trì hoãn load/render một phần template và dependencies của nó theo trigger như viewport, interaction, hover, timer hoặc idle.

Ví dụ:

html
@defer (on viewport; prefetch on idle) {
  <app-heavy-chart />
} @placeholder {
  <app-chart-skeleton />
} @loading {
  <p>Loading chart...</p>
} @error {
  <p>Cannot load chart</p>
}

Dùng cho UI chưa cần ngay để giảm initial bundle và cải thiện Core Web Vitals.

ng-content cho phép parent truyền markup vào layout của child component, tương tự slot.

Ví dụ card có slot tiêu đề và nội dung:

html
<header><ng-content select="[card-title]" /></header>
<section><ng-content /></section>
html
<app-card>
  <h2 card-title>Profile</h2>
  <app-profile />
</app-card>

Dùng khi xây component wrapper như card, modal, tabs, layout shell, nơi child quyết định khung còn parent quyết định nội dung.

Tránh API slot quá phức tạp nếu component chỉ cần vài input đơn giản.

signal giữ state có thể ghi; computed derive giá trị read-only; effect chạy side effect khi dependency signal đổi.

Ví dụ:

typescript
const count = signal(0)
const doubled = computed(() => count() * 2)

effect(() => {
  localStorage.setItem("count", String(count()))
})

count.update(v => v + 1)

Không nên dùng effect để propagate state giữa signals nếu computed hoặc linkedSignal biểu đạt được quan hệ đó rõ hơn.

View query đọc element/component nằm trong template của chính component; content query đọc nội dung parent project vào qua ng-content.

Ví dụ signal query hiện đại:

typescript
@Component({ template: `<input #searchBox />` })
export class SearchPanel {
  searchBox = viewChild<ElementRef<HTMLInputElement>>("searchBox")

  focus() {
    this.searchBox()?.nativeElement.focus()
  }
}

Dùng query khi cần tương tác với child component/directive hoặc DOM API thật.

Không dùng query chỉ để truyền data; việc đó nên qua input/output hoặc service/store.

Observable biểu diễn stream theo thời gian, có cancellation, operators mạnh và phù hợp với HTTP, WebSocket, router events hoặc async pipelines.

Bridge khi cần đưa stream vào template signal-based:

typescript
const user$ = this.http.get<User>("/api/me")
const user = toSignal(user$, { initialValue: null })
const userChanges$ = toObservable(user)

Signal biểu diễn current value đồng bộ, đọc trực tiếp trong template và phù hợp với local UI state.

Trong app thực tế thường dùng cả hai: Observable cho event/data stream, Signal cho state hiển thị.

ChangeDetectionStrategy.OnPush giảm số lần check component bằng cách chỉ cập nhật khi input reference đổi, event trong component xảy ra, async pipe emit, signal được đọc trong template đổi, hoặc component được mark for check.

Nó hiệu quả khi state immutable và component tree lớn. Pitfall: mutate object/array tại chỗ có thể không trigger update như mong muốn; nên replace reference hoặc dùng signal update đúng cách.

async pipe subscribe Observable/Promise, render latest value và tự unsubscribe khi view bị destroy.

Nó giảm memory leak so với subscribe thủ công trong component. Với OnPush, mỗi lần Observable emit, async pipe cũng mark component để update. Nếu cần transform nhiều stream phức tạp, xử lý trong component/service bằng RxJS rồi expose stream đã sẵn sàng cho template.

Pipe transform dữ liệu trong template. Mặc định pipe là pure: Angular chỉ gọi lại khi primitive value đổi hoặc object/array reference đổi, nên hiệu năng tốt hơn.

Ví dụ custom pure pipe:

typescript
@Pipe({ name: "kebabCase" })
export class KebabCasePipe implements PipeTransform {
  transform(value: string): string {
    return value.toLowerCase().replaceAll(" ", "-")
  }
}

Impure pipe pure: false có thể bắt mutation bên trong object/array nhưng chạy rất thường xuyên, dễ gây chậm UI.

Trong phỏng vấn nên nói: ưu tiên pure pipe, immutable data, hoặc computed() thay vì impure pipe.

providedIn: "root" đăng ký service ở root injector, thường là singleton toàn app và tree-shakable nếu không dùng.

Ví dụ component provider tạo store riêng cho mỗi wizard:

typescript
@Component({
  selector: "app-wizard",
  providers: [WizardStore],
  template: "<ng-content />",
})
export class WizardComponent {}

Provider trong component tạo instance ở injector của component đó và áp dụng cho subtree bên dưới.

Dùng root cho shared stateless/service toàn app; dùng component provider khi cần state riêng theo mỗi instance component, ví dụ wizard, tab hoặc embedded feature.

Constructor injection khai báo dependency qua constructor parameters, rõ ràng và quen thuộc.

Ví dụ functional guard dùng inject():

typescript
export const authGuard: CanActivateFn = () => {
  const auth = inject(AuthService)
  const router = inject(Router)

  return auth.isLoggedIn() ? true : router.createUrlTree(["/login"])
}

inject() gọn hơn cho guards, resolvers, interceptors hoặc field initializers; chỉ gọi trong injection context hợp lệ.

InjectionToken dùng khi dependency không phải class concrete, ví dụ config object, primitive value, feature flag, API URL hoặc interface không tồn tại ở runtime.

Ví dụ:

typescript
export const API_URL = new InjectionToken<string>("API_URL")

bootstrapApplication(AppComponent, {
  providers: [{ provide: API_URL, useValue: "/api" }],
})

const apiUrl = inject(API_URL)

Với config cần lazy hoặc phụ thuộc dependency khác, provider có thể dùng useFactory.

useClass tạo instance từ class cụ thể, thường để đổi implementation.

Ví dụ các provider strategy:

typescript
providers: [
  { provide: Logger, useClass: ConsoleLogger },
  { provide: API_URL, useValue: "/api" },
  { provide: SESSION_ID, useFactory: () => crypto.randomUUID() },
  { provide: OLD_LOGGER, useExisting: Logger },
]

useValue cấp object/value có sẵn, useFactory tạo dependency động và có thể inject dependency khác, useExisting alias token này sang token khác để reuse cùng instance.

Với standalone component, route có thể dùng loadComponent; với feature có nhiều child routes, dùng loadChildren để import route array.

Ví dụ:

typescript
export const routes: Routes = [
  {
    path: "admin",
    loadComponent: () => import("./admin/admin.page").then(m => m.AdminPage),
  },
  {
    path: "settings",
    loadChildren: () => import("./settings/routes").then(m => m.SETTINGS_ROUTES),
  },
]

Nên đặt boundary theo feature/screen thật sự, không tách quá nhỏ khiến waterfall request tăng.

Không.

Route guard chỉ kiểm soát navigation phía client để cải thiện UX, ví dụ redirect user chưa login hoặc chặn rời form chưa lưu. Bảo mật thật phải nằm ở backend/API authorization vì user có thể gọi API trực tiếp hoặc sửa code client. Guard nên được xem là lớp UX, không phải security boundary.

Resolver fetch hoặc chuẩn bị data trước khi route activate, giúp component nhận data đã sẵn sàng và xử lý loading/error ở routing layer.

Ví dụ functional resolver:

typescript
export const userResolver: ResolveFn<User> = route => {
  const api = inject(UserApi)
  return api.getUser(route.paramMap.get("id")!)
}

Dùng resolver khi màn hình không có ý nghĩa nếu thiếu data chính, như detail page cần entity theo id.

Không nên dùng cho mọi request; data phụ hoặc có thể stream sau khi render thì fetch trong component/store để route chuyển nhanh hơn.

withComponentInputBinding() bind path params, query params, matrix params và route data vào component inputs có cùng tên.

Ví dụ:

typescript
bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes, withComponentInputBinding())],
})

@Component({ template: "User {{ id() }}" })
export class UserPage {
  id = input.required<string>()
}

Cách này làm component dễ test hơn và giảm coupling với ActivatedRoute.

Cách hiện đại nên ưu tiên withComponentInputBinding() nếu param map trực tiếp vào input. Khi cần đọc router state phức tạp, dùng ActivatedRouteparamMap/queryParamMap.

Ví dụ bridge route param sang signal:

typescript
@Component({ template: "User {{ id() }}" })
export class UserPage {
  private route = inject(ActivatedRoute)
  id = toSignal(this.route.paramMap.pipe(map(params => params.get("id"))), {
    initialValue: null,
  })
}

Snapshot chỉ phù hợp khi param không đổi trong lifetime component.

Nếu route reuse và param có thể đổi, dùng stream hoặc component input binding.

Typed Forms giúp FormControl, FormGroupvalueChanges mang đúng TypeScript type, giảm lỗi truy cập field sai hoặc submit payload sai shape.

Ví dụ:

typescript
const form = new FormGroup({
  email: new FormControl("", { nonNullable: true, validators: [Validators.email] }),
})

const email: string = form.controls.email.value

Trong app lớn, typed forms làm refactor an toàn hơn vì compiler phát hiện form contract bị lệch.

Validator là function nhận AbstractControl và trả về ValidationErrors | null.

Ví dụ cross-field validator:

typescript
function passwordsMatch(group: AbstractControl): ValidationErrors | null {
  const password = group.get("password")?.value
  const confirm = group.get("confirm")?.value

  return password === confirm ? null : { passwordMismatch: true }
}

Async validator phù hợp check server như username đã tồn tại; cần debounce/cancel để không bắn request quá nhiều.

FormArray dùng khi số lượng control động, ví dụ nhiều số điện thoại, danh sách địa chỉ, line items trong invoice hoặc survey questions.

Ví dụ thêm phone control động:

typescript
const phones = new FormArray([
  new FormControl("", { nonNullable: true }),
])

phones.push(new FormControl("", { nonNullable: true }))

Nó giữ collection controls theo index và cho phép push/remove/insert.

  • Nếu mỗi item có nhiều field, thường dùng FormArray<FormGroup<...>>.
  • Cần cẩn thận validation và track trong template để không làm mất state khi thêm/xóa dòng.

Interceptor xử lý cross-cutting concern cho request/response: attach auth token, refresh token, correlation id, logging, retry, error normalization hoặc loading indicator.

Ví dụ functional interceptor:

typescript
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = inject(AuthService).token()
  const authReq = req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })

  return next(authReq)
}

provideHttpClient(withInterceptors([authInterceptor]))

Nên giữ interceptor mỏng; business transform để trong API service.

switchMap hủy request trước và lấy request mới nhất, phù hợp search/autocomplete.

Ví dụ search box:

typescript
results$ = this.search.valueChanges.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(term => this.http.get<SearchResult[]>("/api/search", { params: { q: term } })),
)

mergeMap chạy song song, concatMap xếp hàng giữ thứ tự, exhaustMap bỏ qua trigger mới khi request cũ đang chạy.

Chọn operator theo concurrency semantics.

Ưu tiên async pipe trong template vì tự unsubscribe.

Khi phải subscribe trong class, dùng takeUntilDestroyed() với DestroyRef:

typescript
private destroyRef = inject(DestroyRef)

ngOnInit() {
  this.router.events
    .pipe(takeUntilDestroyed(this.destroyRef))
    .subscribe(event => this.trackNavigation(event))
}

Không cần unsubscribe với HTTP Observable hoàn tất một lần, nhưng vẫn cần cẩn thận với streams dài như router events, form valueChanges, interval, WebSocket hoặc Subject từ service.

Lỗi HTTP nên được xử lý ở đúng tầng: component/store xử lý lỗi nghiệp vụ cần hiển thị cụ thể; API service normalize response; interceptor xử lý cross-cutting concern như auth, retry, correlation id hoặc global error logging.

Ví dụ service trả fallback có kiểm soát:

typescript
loadUsers() {
  return this.http.get<User[]>("/api/users").pipe(
    retry({ count: 2, delay: 500 }),
    catchError(error => {
      this.logger.error(error)
      return of([])
    }),
  )
}

Không nuốt mọi lỗi ở interceptor vì component sẽ mất ngữ cảnh để hiển thị message đúng.

SSR render HTML trên server để cải thiện first content, SEO và perceived performance.

Cấu hình hydration phía client thường nằm ở bootstrap:

typescript
bootstrapApplication(AppComponent, {
  providers: [provideClientHydration(withEventReplay())],
})

Hydration tái sử dụng HTML đã render thay vì vẽ lại toàn bộ trên client.

Cần đảm bảo server/client render deterministic; code dùng window, random, timezone hoặc DOM measurement phải chạy đúng browser-only guard.

Các lỗi phổ biến: render dựa vào Date.now()/random, đọc window hoặc localStorage khi server render, format ngày theo timezone khác nhau, mutate DOM bằng library ngoài Angular trước hydration, hoặc API trả data khác giữa server và client.

Guard browser-only code:

typescript
const platformId = inject(PLATFORM_ID)
if (isPlatformBrowser(platformId)) {
  localStorage.setItem("seen", "true")
}

Cách fix là làm render deterministic, dùng transfer cache/data, platform checks và defer DOM-dependent widgets.

Bắt đầu bằng route-level lazy loading, kiểm tra bundle analyzer/source-map-explorer, loại bỏ dependency nặng không cần thiết và dùng @defer cho UI chưa cần ngay.

Tiếp theo tối ưu shared module/imports để tránh kéo cả thư viện vào initial chunk. Đừng micro-optimize trước khi đo; mục tiêu là giảm JavaScript critical path của màn hình đầu tiên.

Angular Aria là package @angular/aria gồm các headless accessible directives cho những pattern WAI-ARIA phổ biến như toolbar, tabs, menu, listbox, combobox, tree và grid.

Ví dụ toolbar headless:

html
<div ngToolbar aria-label="Text tools">
  <button ngToolbarWidget value="bold" type="button" aria-label="bold">B</button>
  <button ngToolbarWidget value="italic" type="button" aria-label="italic">I</button>
</div>

Nó không thay thế design system hay Angular Material; nó xử lý keyboard interaction, ARIA attributes, focus management và screen reader support để team tự style UI an toàn hơn.

animate.enteranimate.leave là API animation mới được compiler hỗ trợ trực tiếp, dùng CSS class hoặc callback khi element vào/rời DOM. Docs Angular hiện tại khuyến nghị native CSS với hai API này cho code mới; nhiều API trong @angular/animations legacy đã bị deprecate.

Ví dụ:

html
@if (open()) {
  <section animate.enter="fade-in" animate.leave="fade-out">Panel</section>
}

Điểm cần nhớ: nếu dùng callback cho animate.leave, phải gọi animationComplete() để Angular remove element đúng lúc.

Angular docs hiện tại mô tả project CLI mới dùng Vitest với jsdom mặc định, trong khi Karma vẫn được hỗ trợ cho migration/legacy.

Khi phỏng vấn, nên nói rõ bối cảnh version: codebase cũ có thể vẫn Karma/Jasmine, project mới nên cân nhắc Vitest; E2E vẫn là lớp riêng như Playwright/Cypress.

Dùng provideHttpClient() cùng provideHttpClientTesting() để thay backend thật bằng test backend, rồi dùng HttpTestingController để assert request và flush response.

Ví dụ:

typescript
TestBed.configureTestingModule({
  providers: [UserApi, provideHttpClient(), provideHttpClientTesting()],
})

const api = TestBed.inject(UserApi)
const http = TestBed.inject(HttpTestingController)
const promise = firstValueFrom(api.getUser("42"))

http.expectOne("/api/users/42").flush({ id: "42", name: "Ada" })
await expectAsync(promise).toBeResolved()
http.verify()

Thứ tự provider quan trọng: provideHttpClient() phải đứng trước provideHttpClientTesting().

Không phải Angular app nào cũng cần global state library. Local component state nên dùng signals; server stream/cache có thể giữ Observable trong service; feature state vừa phải có thể dùng service/store tự viết.

Pattern store nhỏ bằng signal:

typescript
@Injectable({ providedIn: "root" })
export class CartStore {
  private readonly _items = signal<CartItem[]>([])
  readonly items = this._items.asReadonly()
  readonly total = computed(() => this._items().reduce((sum, item) => sum + item.price, 0))
}

Cân nhắc NgRx/Signal Store khi state dùng chung nhiều route, mutation phức tạp, cần devtools/time travel, effect orchestration hoặc convention mạnh cho team lớn.