Rumah >hujung hadapan web >tutorial js >Pengaturcaraan Fungsian dengan fp-ts dalam Node.js

Pengaturcaraan Fungsian dengan fp-ts dalam Node.js

Linda Hamilton
Linda Hamiltonasal
2024-12-31 01:08:17429semak imbas

Functional Programming with fp-ts in Node.js

pengenalan

Pengaturcaraan fungsional (FP) telah mendapat populariti kerana kebolehkomposisian, kebolehujian dan keteguhannya. Dalam ekosistem JavaScript, perpustakaan seperti fp-ts membawa konsep FP yang berkuasa kepada TypeScript, membolehkan anda menulis kod yang lebih bersih dan lebih dipercayai.

Artikel ini meneroka fp-ts konsep seperti Option, Either, Task, Reader dan ReaderTaskEither. Kami akan membina apl CRUD asas menggunakan fp-ts, pg (klien PostgreSQL) dan Express.js untuk melihat bagaimana abstraksi ini bersinar secara nyata- aplikasi dunia.


Konsep Utama

Sebelum menyelam ke dalam apl, mari kita bincangkan secara ringkas konsep utama:

  1. Pilihan: Memodelkan kehadiran atau ketiadaan nilai (Sesetengah atau Tiada).
  2. Sama ada: Mewakili pengiraan yang boleh berjaya (Kanan) atau gagal (Kiri).
  3. Tugas: Mewakili pengiraan tak segerak yang malas.
  4. Pembaca: Menyuntik kebergantungan ke dalam pengiraan.
  5. ReaderTaskEither: Menggabungkan Pembaca, Tugas dan Sama ada untuk operasi async dengan kebergantungan dan pengendalian ralat.

Menyediakan Projek

Mulakan Projek

mkdir fp-ts-crud && cd fp-ts-crud
npm init -y
npm install express pg fp-ts io-ts
npm install --save-dev typescript @types/express ts-node-dev jest @types/jest ts-jest

Sediakan TypeScript

Buat tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

Struktur Projek

src/
  index.ts        # Entry point
  db.ts           # Database setup
  models/         # Data models and validation
  services/       # Business logic
  controllers/    # CRUD operations
  utils/          # fp-ts utilities
  errors/         # Custom error classes

Melaksanakan Aplikasi CRUD

Persediaan Pangkalan Data (db.ts)

import { Pool } from 'pg';

export const pool = new Pool({
  user: 'postgres',
  host: 'localhost',
  database: 'fp_ts_crud',
  password: 'password',
  port: 5432,
});

Tentukan Model dan Pengesahan (model/User.ts)

import * as t from 'io-ts';
import { isRight } from 'fp-ts/Either';

export const User = t.type({
  id: t.number,
  name: t.string,
  email: t.string,
});

export const validateUser = (data: unknown): t.TypeOf<typeof User> | null => {
  const result = User.decode(data);
  return isRight(result) ? result.right : null;
};

Pengendalian Ralat Tersuai (ralat/AppError.ts)

export class AppError extends Error {
  constructor(public statusCode: number, public code: string, public message: string) {
    super(message);
    this.name = 'AppError';
  }
}

export const createAppError = (statusCode: number, code: string, message: string): AppError => {
  return new AppError(statusCode, code, message);
};

Lapisan Perkhidmatan (services/UserService.ts)

import { pool } from '../db';
import { ReaderTaskEither, right, left } from 'fp-ts/ReaderTaskEither';
import { pipe } from 'fp-ts/function';
import { createAppError, AppError } from '../errors/AppError';

type Dependencies = { db: typeof pool };
type User = { name: string; email: string };

export const createUser = (
  user: User
): ReaderTaskEither<Dependencies, AppError, string> => (deps) => async () => {
  try {
    const result = await deps.db.query(
      'INSERT INTO users (name, email) VALUES (, ) RETURNING id',
      [user.name, user.email]
    );
    return right(`User created with ID: ${result.rows[0].id}`);
  } catch (error) {
    return left(createAppError(500, 'USER_CREATION_FAILED', 'Failed to create user'));
  }
};

export const getUser = (
  id: number
): ReaderTaskEither<Dependencies, AppError, { id: number; name: string; email: string }> => (deps) => async () => {
  try {
    const result = await deps.db.query('SELECT * FROM users WHERE id = ', [id]);
    return result.rows[0]
      ? right(result.rows[0])
      : left(createAppError(404, 'USER_NOT_FOUND', 'User not found'));
  } catch {
    return left(createAppError(500, 'USER_FETCH_FAILED', 'Failed to fetch user'));
  }
};

Operasi CRUD (pengawal/UserController.ts)

import { pipe } from 'fp-ts/function';
import { createUser, getUser } from '../services/UserService';
import { pool } from '../db';
import { AppError } from '../errors/AppError';

const errorHandler = (err: unknown, res: express.Response): void => {
  if (err instanceof AppError) {
    res.status(err.statusCode).json({ error: { code: err.code, message: err.message } });
  } else {
    res.status(500).json({ error: { code: 'UNKNOWN_ERROR', message: 'An unexpected error occurred' } });
  }
};

export const createUserHandler = (req: express.Request, res: express.Response): void => {
  pipe(
    createUser(req.body),
    (task) => task({ db: pool }),
    (promise) =>
      promise.then((result) =>
        result._tag === 'Left'
          ? errorHandler(result.left, res)
          : res.json({ message: result.right })
      )
  );
};

export const getUserHandler = (req: express.Request, res: express.Response): void => {
  pipe(
    getUser(parseInt(req.params.id, 10)),
    (task) => task({ db: pool }),
    (promise) =>
      promise.then((result) =>
        result._tag === 'Left'
          ? errorHandler(result.left, res)
          : res.json(result.right)
      )
  );
};

Express API (index.ts)

import express from 'express';
import { createUserHandler, getUserHandler } from './controllers/UserController';

const app = express();
app.use(express.json());

// Routes
app.post('/users', createUserHandler);
app.get('/users/:id', getUserHandler);

// Start Server
app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

Menjalankan Apl dengan Docker dan Docker Compose

Dockerfile

# Stage 1: Build
FROM node:22 AS builder
WORKDIR /app
COPY package*.json .
RUN npm install
COPY . .
RUN npm run build

# Stage 2: Run
FROM node:22
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm install --production
CMD ["node", "dist/index.js"]

docker-compose.yml

version: '3.8'
services:
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: fp_ts_crud
    ports:
      - "5432:5432"
    volumes:
      - db_data:/var/lib/postgresql/data
volumes:
  db_data:

Jalankan Apl - Mod Pembangunan

# Start the database
docker-compose up -d

# Run the app
npx ts-node-dev src/index.ts

Jalankan Apl - Mod Pengeluaran

# Build the docker image
docker build -t fp-ts-crud-app .

# Start the database
docker-compose up -d

# Run the container
docker run -p 3000:3000 fp-ts-crud-app

Ujian Penulisan

Sediakan Jest

Kemas kini skrip package.json:

mkdir fp-ts-crud && cd fp-ts-crud
npm init -y
npm install express pg fp-ts io-ts
npm install --save-dev typescript @types/express ts-node-dev jest @types/jest ts-jest

Contoh Ujian (__tests__/UserService.test.ts)

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

Kesimpulan

Dengan memanfaatkan fp-ts, Docker dan pengendalian ralat yang mantap, kami membina aplikasi CRUD Node.js yang berfungsi, berskala dan boleh diselenggara. Menggunakan corak pengaturcaraan berfungsi menjadikan kod anda lebih boleh diramal dan boleh dipercayai, terutamanya apabila mengendalikan aliran kerja tak segerak.

Atas ialah kandungan terperinci Pengaturcaraan Fungsian dengan fp-ts dalam Node.js. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn