发送 Index 请求
让我们添加索引图书的 API,然后放入一些测试数据供后续使用。
移动 domain/model 到 src/domain/model。
创建 src/domain/gateway/book_manager.ts:
import { Book } from "../model";
export interface BookManager {
indexBook(b: Book): Promise<string>;
}
此处也是在使用4层架构。项目越大,该架构的好处越明显。阅读更多。
添加 src/domain/gateway/index.ts:
export { BookManager } from "./book_manager";
创建 src/infrastructure/search/es.ts:
import { Client } from "@elastic/elasticsearch";
import { Book } from "../../domain/model";
import { BookManager } from "../../domain/gateway";
const INDEX_BOOK = "book_idx";
export class ElasticSearchEngine implements BookManager {
private client: Client;
private page_size: number;
constructor(address: string, page_size: number) {
this.page_size = page_size;
this.client = new Client({ node: address });
}
async indexBook(b: Book): Promise<string> {
const result = await this.client.index({
index: INDEX_BOOK,
document: b,
});
return result._id;
}
}
默认情况下,Elasticsearch 允许你将文档索引到尚不存在的索引中。 当你将文档索引到不存在的索引时,Elasticsearch 将会使用默认设置动态地创建索引。这在开发者不想显式地创建索引时会很方便。
创建 src/infrastructure/config/config.ts:
import { readFileSync } from "fs";
interface SearchConfig {
address: string;
}
interface ApplicationConfig {
port: number;
page_size: number;
}
export interface Config {
app: ApplicationConfig;
search: SearchConfig;
}
export function parseConfig(filename: string): Config {
return JSON.parse(readFileSync(filename, "utf-8"));
}
添加 src/infrastructure/config/index.ts:
export { Config, parseConfig } from "./config";
创建 config.json:
{
"app": {
"port": 3000,
"page_size": 10
},
"search": {
"address": "http://localhost:9200"
}
}
警醒:
不要直接 git 提交 config.json。可能会导致敏感数据泄露。如果非要提交的话,建议只提交配置格式模板。
比如:{ "app": { "port": 3000, "page_size": 10 }, "search": { "address": "" } }
创建 src/application/executor/book_operator.ts:
import { BookManager } from "../../domain/gateway";
import { Book } from "../../domain/model";
export class BookOperator {
private bookManager: BookManager;
constructor(b: BookManager) {
this.bookManager = b;
}
async createBook(b: Book): Promise<string> {
return await this.bookManager.indexBook(b);
}
}
记得给所有相关子目录创建 index.ts 文件。
创建 src/application/wire_helper.ts:
import { Config } from "../infrastructure/config";
import { BookManager } from "../domain/gateway";
import { ElasticSearchEngine } from "../infrastructure/search";
// WireHelper is the helper for dependency injection
export class WireHelper {
private engine: ElasticSearchEngine;
constructor(c: Config) {
this.engine = new ElasticSearchEngine(c.search.address, c.app.page_size);
}
bookManager(): BookManager {
return this.engine;
}
}
创建 src/adapter/router.ts:
import express, { Request, Response } from "express";
import { Book } from "../domain/model";
import { BookOperator } from "../application/executor";
import { WireHelper } from "../application";
class RestHandler {
private bookOperator: BookOperator;
constructor(bookOperator: BookOperator) {
this.bookOperator = bookOperator;
}
// Create a new book
public async createBook(req: Request, res: Response): Promise<void> {
try {
const bookID = await this.bookOperator.createBook(req.body as Book);
res.status(201).json({ id: bookID });
} catch (err) {
console.error(`Failed to create: ${err}`);
res.status(404).json({ error: "Failed to create" });
}
}
}
// Create router
function MakeRouter(wireHelper: WireHelper): express.Router {
const restHandler = new RestHandler(
new BookOperator(wireHelper.bookManager())
);
const router = express.Router();
router.get("/", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
router.post("/books", restHandler.createBook.bind(restHandler));
return router;
}
export function InitApp(wireHelper: WireHelper): express.Express {
const app = express();
// Middleware to parse JSON bodies
app.use(express.json());
const r = MakeRouter(wireHelper);
app.use("", r);
return app;
}
用以下代码替换 main.ts 中内容:
import { WireHelper } from "./application";
import { InitApp } from "./adapter";
import { parseConfig } from "./infrastructure/config";
const CONFIG_FILENAME = "config.json";
const c = parseConfig(CONFIG_FILENAME);
const wireHelper = new WireHelper(c);
const app = InitApp(wireHelper);
app.listen(c.app.port, () => {
console.log(`Running on port ${c.app.port}`);
});
再次运行 server,然后使用 curl
进行测试:
npm run dev
样例请求:
curl -X POST \
-H "Content-Type: application/json" \
-d '{"title":"The Da Vinci Code","author":"Dan Brown","published_at":"2003-03-18","content":"In the Louvre, a curator is found dead. Next to his body, an enigmatic message. It is the beginning of a race to discover the truth about the Holy Grail."}' \
http://localhost:3000/books
样例响应:
{"id":"1jxTDo8BWkofwNB1TRnC"}
放入测试数据
curl -X POST \
-H "Content-Type: application/json" \
-d '{"title":"Harry Potter and the Philosopher\u0027s Stone","author":"J.K. Rowling","published_at":"1997-06-26","content":"A young boy discovers he is a wizard and begins his education at Hogwarts School of Witchcraft and Wizardry, where he uncovers the mystery of the Philosopher‘s Stone."}' \
http://localhost:3000/books
curl -X POST \
-H "Content-Type: application/json" \
-d '{"title":"To Kill a Mockingbird","author":"Harper Lee","published_at":"1960-07-11","content":"Set in the American South during the Great Depression, the novel explores themes of racial injustice and moral growth through the eyes of young Scout Finch."}' \
http://localhost:3000/books
curl -X POST \
-H "Content-Type: application/json" \
-d '{"title":"The Lord of the Rings","author":"J.R.R. Tolkien","published_at":"1954-07-29","content":"A hobbit named Frodo Baggins embarks on a perilous journey to destroy a powerful ring and save Middle-earth from the Dark Lord Sauron."}' \
http://localhost:3000/books
curl -X POST \
-H "Content-Type: application/json" \
-d '{"title":"The Catcher in the Rye","author":"J.D. Salinger","published_at":"1951-07-16","content":"Holden Caulfield narrates his experiences in New York City after being expelled from prep school, grappling with themes of alienation, identity, and innocence."}' \
http://localhost:3000/books
curl -X POST \
-H "Content-Type: application/json" \
-d '{"title":"The Alchemist","author":"Paulo Coelho","published_at":"1988-01-01","content":"Santiago, a shepherd boy, travels from Spain to Egypt in search of a treasure buried near the Pyramids. Along the way, he learns about the importance of following one‘s dreams."}' \
http://localhost:3000/books
curl -X POST \
-H "Content-Type: application/json" \
-d '{"title":"The Hunger Games","author":"Suzanne Collins","published_at":"2008-09-14","content":"In a dystopian future, teenagers are forced to participate in a televised death match called the Hunger Games. Katniss Everdeen volunteers to take her sister‘s place and becomes a symbol of rebellion."}' \
http://localhost:3000/books
curl -X POST \
-H "Content-Type: application/json" \
-d '{"title":"1984","author":"George Orwell","published_at":"1949-06-08","content":"Winston Smith lives in a totalitarian society ruled by the Party led by Big Brother. He rebels against the oppressive regime but ultimately succumbs to its control."}' \
http://localhost:3000/books
curl -X POST \
-H "Content-Type: application/json" \
-d '{"title":"The Girl with the Dragon Tattoo","author":"Stieg Larsson","published_at":"2005-08-01","content":"Journalist Mikael Blomkvist and hacker Lisbeth Salander investigate the disappearance of a young woman from a wealthy family, uncovering dark secrets and corruption."}' \
http://localhost:3000/books
curl -X POST \
-H "Content-Type: application/json" \
-d '{"title":"Gone Girl","author":"Gillian Flynn","published_at":"2012-06-05","content":"On their fifth wedding anniversary, Nick Dunne‘s wife, Amy, disappears. As the media circus ensues and suspicions mount, Nick finds himself in a whirlwind of deception and betrayal."}' \
http://localhost:3000/books