设计就是把事物拆分。。。用一种之后可以拼接在一起的方法。。。把事物拆分成可以组合在一起的事物就是设计 -- Rich Hickey《设计、重组和性能》
Clean Architecture
src/
|_domain/
|_user.ts
|_product.ts
|_order.ts
|_cart.ts
|_application/
|_addToCart.ts
|_authenticate.ts
|_orderProducts.ts
|_ports.ts
|_services/
|_authAdapter.ts
|_notificationAdapter.ts
|_paymentAdapter.ts
|_storageAdapter.ts
|_api.ts
|_store.tsx
|_lib/
|_ui/
- product
- user
- order
- shopping cart
// domain/user.ts
export type UserName = string;
export type User = {
id: UniqueId;
name: UserName;
email: Email;
preferences: Ingredient[];
allergies: Ingredient[];
};
// domain/product.ts
export type ProductTitle = string;
export type Product = {
id: UniqueId;
title: ProductTitle;
price: PriceCents;
toppings: Ingredient[];
};
// domain/cart.ts
import { Product } from "./product";
export type Cart = {
products: Product[];
};
// domain/order.ts
export type OrderStatus = "new" | "delivery" | "completed";
export type Order = {
user: UniqueId;
cart: Cart;
created: DateTimeString;
status: OrderStatus;
total: PriceCents;
};
// domain/user.ts
export function hasAllergy(user: User, ingredient: Ingredient): boolean {
return user.allergies.includes(ingredient);
}
export function hasPreference(user: User, ingredient: Ingredient): boolean {
return user.preferences.includes(ingredient);
}
// domain/cart.ts
export function addProduct(cart: Cart, product: Product): Cart {
return { ...cart, products: [...cart.products, product] };
}
export function contains(cart: Cart, product: Product): boolean {
return cart.products.some(({ id }) => id === product.id);
}
// domain/product.ts
export function totalPrice(products: Product[]): PriceCents {
return products.reduce((total, { price }) => total + price, 0);
}
// domain/order.ts
export function createOrder(user: User, cart: Cart): Order {
return {
user: user.id,
cart,
created: new Date().toISOString(),
status: "new",
total: totalPrice(products),
};
}
// shared-kernel.d.ts
type Email = string;
type UniqueId = string;
type DateTimeString = string;
type PriceCents = number;
type OrderProducts = (user: User, cart: Cart) => Promise<void>;
// application/ports.ts
export interface PaymentService {
tryPay(amount: PriceCents): Promise<boolean>;
}
// application/ports.ts
export interface NotificationService {
notify(message: string): void;
}
// application/ports.ts
export interface OrdersStorageService {
orders: Order[];
updateOrders(orders: Order[]): void;
}
// application/orderProducts.ts
const payment: PaymentService = {};
const notifier: NotificationService = {};
const orderStorage: OrdersStorageService = {};
// application/orderProducts.ts
//...
async function orderProducts(user: User, cart: Cart) {
const order = createOrder(user, cart);
}
// application/orderProducts.ts
//...
async function orderProducts(user: User, cart: Cart) {
const order = createOrder(user, cart);
// Try to pay for the order;
// Notify the user if something is wrong:
const paid = await payment.tryPay(order.total);
if (!paid) return notifier.notify("Oops! 🤷");
// Save the result and clear the cart:
const { orders } = orderStorage;
orderStorage.updateOrders([...orders, order]);
cartStorage.emptyCart();
}
// ui/components/Buy.tsx
export function Buy() {
// Get access to the use case in the component:
const { orderProducts } = useOrderProducts();
async function handleSubmit(e: React.FormEvent) {
setLoading(true);
e.preventDefault();
// Call the use case function:
await orderProducts(user!, cart);
setLoading(false);
}
return (
<section>
<h2>Checkout</h2>
<form onSubmit={handleSubmit}>{/* ... */}</form>
</section>
);
}
// application/orderProducts.ts
export function useOrderProducts() {
const notifier = useNotifier();
const payment = usePayment();
const orderStorage = useOrdersStorage();
async function orderProducts(user: User, cookies: Cookie[]) {
// …
}
return { orderProducts };
}
// services/paymentAdapter.ts
import { fakeApi } from "./api";
import { PaymentService } from "../application/ports";
export function usePayment(): PaymentService {
return {
tryPay(amount: PriceCents) {
return fakeApi(true);
},
};
}
// services/api.ts
export function fakeApi<TResponse>(response: TResponse): Promise<TResponse> {
return new Promise((res) => setTimeout(() => res(response), 450));
}
// services/notificationAdapter.ts
import { NotificationService } from "../application/ports";
export function useNotifier(): NotificationService {
return {
notify: (message: string) => window.alert(message),
};
}
// store.tsx
const StoreContext = React.createContext<any>({});
export const useStore = () => useContext(StoreContext);
export const Provider: React.FC = ({ children }) => {
// ...Other entities...
const [orders, setOrders] = useState([]);
const value = {
// ...
orders,
updateOrders: setOrders,
};
return (
<StoreContext.Provider value={value}>{children}</StoreContext.Provider>
);
};
// services/storageAdapter.ts
export function useOrdersStorage(): OrdersStorageService {
return useStore();
}
// shared-kernel.d.ts
type PriceCents = number;
type Currency = "RUB" | "USD" | "EUR" | "SEK";
type AmountCents = number;
type Price = {
value: AmountCents;
currency: Currency;
};
import { Product, totalPrice } from "./product";
export function createOrder(user: User, cart: Cart): Order {
return {
user: user.id,
cart,
created: new Date().toISOString(),
status: "new",
total: totalPrice(products),
};
}
import { Product, totalPrice } from "./product";
export function createOrder(user: User, cart: Cart): Order {
return {
user: user.id,
cart,
// Вот эта строка:
created: new Date().toISOString(),
status: "new",
total: totalPrice(products),
};
}
// lib/datetime.ts
export function currentDatetime(): DateTimeString {
return new Date().toISOString();
}
// domain/order.ts
import { currentDatetime } from "../lib/datetime";
import { Product, totalPrice } from "./product";
export function createOrder(user: User, cart: Cart): Order {
return {
user: user.id,
cart,
created: currentDatetime(),
status: "new",
total: totalPrice(products),
};
}
// domain/order.ts
export function createOrder(
user: User,
cart: Cart,
created: DateTimeString
): Order {
return {
user: user.id,
products,
created,
status: "new",
total: totalPrice(products),
};
}
function someUserCase() {
// Use the `dateTimeSource` adapter,
// to get the current date in the desired format:
const createdOn = dateTimeSource.currentDatetime();
// Pass already created date to the domain function:
createOrder(user, cart, createdOn);
}
export type Cart = {
products: Product[];
};
export type Order = {
user: UniqueId;
cart: Cart;
created: DateTimeString;
status: OrderStatus;
total: PriceCents;
};
type ProductList = Product[];
type Cart = {
products: ProductList;
};
type Order = {
user: UniqueId;
products: ProductList;
created: DateTimeString;
status: OrderStatus;
total: PriceCents;
};
// application/orderProducts.ts
export function useOrderProducts() {
const notifier = useNotifier();
const payment = usePayment();
const orderStorage = useOrdersStorage();
const cartStorage = useCartStorage();
async function orderProducts(user: User, cart: Cart) {
const order = createOrder(user, cart);
const paid = await payment.tryPay(order.total);
if (!paid) return notifier.notify("Oops! 🤷");
const { orders } = orderStorage;
orderStorage.updateOrders([...orders, order]);
cartStorage.emptyCart();
}
return { orderProducts };
}
type Dependencies = {
notifier?: NotificationService;
payment?: PaymentService;
orderStorage?: OrderStorageService;
};
async function orderProducts(
user: User,
cart: Cart,
dependencies: Dependencies = defaultDependencies
) {
const { notifier, payment, orderStorage } = dependencies;
// ...
}
function useOrderProducts() {
const notifier = useNotifier();
const payment = usePayment();
const orderStorage = useOrdersStorage();
return (user: User, cart: Cart) =>
orderProducts(user, cart, {
notifier,
payment,
orderStorage,
});
}
export function useOrderProducts() {
// Here we use hooks to get the instances of each service,
// which will be used inside the orderProducts use case:
const notifier = useNotifier();
const payment = usePayment();
const orderStorage = useOrdersStorage();
const cartStorage = useCartStorage();
async function orderProducts(user: User, cart: Cart) {
// ...Inside the use case we use those services.
}
return { orderProducts };
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。