基于TDD与清洁架构的React Native住宿预订功能:网关设计及交互器实现优化问询
哥们,我完全懂你现在的困惑——在React Native里用清洁架构开发预订类功能时,很容易一不小心就把前端交互器写成了后端业务逻辑的翻版,尤其是涉及到一堆验证规则和错误处理的时候,写完一看:这怎么跟后端代码一模一样?别慌,咱们一步步梳理清楚。
首先得明确清洁架构里前端层和后端业务层的边界:你现在的BookAHousingInteractor里把大量后端该管的业务验证(比如入住日期不能早于今天、旅客数要符合房源的上下限)都硬塞进了前端,这就是问题根源。这些核心业务规则应该是后端的职责,前端只需要做轻量的前置校验(比如检查日期格式是否合法、旅客数是不是正整数),真正的业务验证交给后端去做,前端只负责把请求发过去,然后处理后端返回的结果。
关于你问的:要不要写带book方法的BookingGateway?
答案是必须要!这正是清洁架构里适配层的核心作用——把前端和后端的通信细节封装起来,给上层(交互器)提供清晰、易用的接口。
具体来说,你可以这么设计:
- 先定义一个
BookingGateway接口,明确对外暴露的方法:
// 定义请求和响应类型 interface BookHousingRequest { housingId: string; checkInDate: Date; checkOutDate: Date; numberOfTravelers: number; } type BookHousingResult = | { success: true; bookingId: string } | { success: false; errors: string[] }; interface BookingGateway { book(request: BookHousingRequest): Promise<BookHousingResult>; }
- 然后实现一个
ApiBookingGateway,负责实际的HTTP通信(用你项目里的请求库,比如axios、fetch或者React Native的fetch):
export class ApiBookingGateway implements BookingGateway { constructor(private authGateway: AuthenticationGateway) {} async book(request: BookHousingRequest): Promise<BookHousingResult> { try { const token = this.authGateway.getAuthenticatedUser()?.token; if (!token) { return { success: false, errors: ["Authentification obligatoire."] }; } const response = await fetch('/api/bookings', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify({ housingId: request.housingId, checkInDate: request.checkInDate.toISOString(), checkOutDate: request.checkOutDate.toISOString(), numberOfTravelers: request.numberOfTravelers }) }); if (!response.ok) { const errorData = await response.json(); return { success: false, errors: errorData.errors || ['预订失败,请稍后重试'] }; } const data = await response.json(); return { success: true, bookingId: data.bookingId }; } catch (error) { // 处理网络错误 return { success: false, errors: ['网络连接失败,请检查网络后重试'] }; } } }
重构你的BookAHousingInteractor
现在交互器的职责就清晰了:协调流程、做轻量前置校验、调用网关、把结果交给Presenter,而不是自己处理一堆业务规则。重构后的代码大概是这样:
export default class BookAHousingInteractor { constructor( private presenter: BookAHousingOutputPort, private bookingGateway: BookingGateway, private dateTimeProvider: DateTimeProvider ) {} async execute(requestModel: BookAHousingRequestModel): Promise<void> { // 前端轻量前置校验:只做格式/基础合法性检查 const errors: string[] = []; const today = this.dateTimeProvider.now(); if (requestModel.checkInDate < today) { errors.push("La date d'arrivé doit être égale ou supérieure à la date du jour."); } if (requestModel.checkOutDate <= today) { errors.push("La date de départ doit être supérieure à la date du jour."); } if (requestModel.numberOfTravelers < 1) { errors.push("Le nombre de voyageurs doit être au moins 1."); } if (errors.length > 0) { this.presenter.present({ errors }); return; } // 调用网关发起预订请求 const result = await this.bookingGateway.book({ housingId: requestModel.housingId, checkInDate: requestModel.checkInDate, checkOutDate: requestModel.checkOutDate, numberOfTravelers: requestModel.numberOfTravelers }); // 根据网关返回结果构建响应模型 const responseModel: BookAHousingResponseModel = result.success ? { bookingId: result.bookingId } : { errors: result.errors }; this.presenter.present(responseModel); } }
测试策略怎么调整?
现在各层职责清晰,测试也变得简单多了:
- 测试网关:用Jest Mock模拟
fetch请求,测试不同场景:- 后端返回201成功时,网关是否正确返回带bookingId的成功结果;
- 后端返回400错误时,网关是否正确解析错误数组;
- 网络请求失败时,网关是否返回网络错误提示。
- 测试交互器:Mock
BookingGateway和Presenter,测试:- 前置校验失败时,是否正确展示对应错误;
- 网关返回成功时,是否通知Presenter展示预订成功;
- 网关返回错误时,是否通知Presenter展示后端返回的错误列表;
- 未登录场景(由网关处理)是否正确返回认证错误。
- UI测试:用React Native Testing Library测试用户操作流程(比如填写表单、点击预订按钮),验证UI是否正确展示加载状态、成功提示或错误信息。
为什么只读功能没这个问题?
因为只读功能(比如获取房源列表、查看房源详情)通常只需要从后端拿数据,前端交互器的职责就是调用网关获取数据,然后交给Presenter展示,不会涉及到复杂的业务验证逻辑,所以边界感自然就清晰了。但预订这类写操作,很容易忍不住把后端的业务规则搬到前端,导致代码臃肿且和后端重复。
最后总结一下
核心就是明确各层职责:
- 后端:负责核心业务规则验证、数据持久化;
- 网关(适配层):封装前端和后端的通信细节,统一请求/响应格式,处理网络错误;
- 交互器(应用层):协调前端流程,做轻量前置校验,调用网关,传递结果给Presenter;
- Presenter(界面适配层):根据交互器的结果更新UI。
这样你的代码会更清晰,也更容易维护和测试,再也不会写出“像后端代码”的前端交互器啦!
内容来源于stack exchange

