액션, 계산, 데이터 구분 실습

2024. 1.29.

Functional Programming

INDEX

쏙쏙 들어오는 함수형 코딩 Ch.1 부터 Ch.3 까지 학습한 내용을 바탕으로 액션과 계산, 데이터를 나누는 연습을 하고자 합니다. 다음과 같은 상황을 만들고 이에 필요한 액션과 계산 데이터를 설계한 후 TypeScript와 Express, MySQL 환경을 가정하여 코드로 구현까지 진행해보겠습니다.


1. 상황

고객은 웹사이트를 통해 음식을 주문할 수 있습니다. 고객은 메뉴에서 음식을 선택하고, 주문을 완료하기 위해 결제 정보를 입력합니다. 주문이 완료되면, 주방에 주문 내역이 전송되고, 음식 준비가 시작됩니다. 준비된 음식은 배달부를 통해 고객에게 배달됩니다.


2. 과정

  1. 고객 주문:
    • 고객이 웹사이트에서 메뉴를 확인하고, 원하는 음식을 선택합니다.
    • 고객이 결제 정보를 입력하고, 주문을 확인합니다.
  2. 주방 처리:
    • 주문이 주방에 전달됩니다.
    • 주방에서는 주문된 음식을 준비합니다.
  3. 음식 배달:
    • 준비된 음식은 배달부에게 전달됩니다.
    • 배달부는 주소에 따라 음식을 고객에게 배달합니다.

3. Level 1

액션

  • 메뉴 리스트 DB 가져오기
  • 주문 접수하기
  • 결제 처리
  • 주문 내역 DB에 저장
  • 주방으로 주문 전달
  • 배달 시작 알림

계산

  • 총 주문 금액 계산
  • 할인 금액 계산
  • 주문 가능 여부 확인

데이터

  • 메뉴 리스트
    • 주문 가능한 음식 목록
    • 품절된 음식 목록
  • 주문 리스트
    • 결제정보
    • 고객 id
    • 주소
    • 주문한 음식명
    • 주문한 음식 수
  • 고객 목록

4. Level 2

액션

  • 고객 인터페이스 관리
    • 고객의 웹사이트 접속
    • 고객의 메뉴 탐색
    • 고객의 메뉴 선택, 주문
    • 고객의 결제정보 입력
  • 주문 처리
    • 주문 정보 검증
    • 결제 처리
    • 주문 상태 업데이트
      • 접수 대기 중
      • 접수 완료
  • 주방 관리
    • 주방에 주문 내역 전달
    • 주문 상태 관리
      • 음식 조리 중
      • 라이더에게 전달 완료
  • 배달 관리
    • 준비된 음식 배달부에게 전달
    • 배달 상태 업데이트
      • 배달 중
      • 배달 완료

계산

  • 금액 계산
    • 각 메뉴의 가격과 선택된 수량에 따른 총금액 계산
    • 적용 가능한 할인 계산하여 최종 결제 금액 산출
  • 주문 검증
    • 메뉴 재고와 주문 수량 비교
    • 주문 가능 여부 결정
  • 배달 시간 계산
    • 주방의 현재 상태, 주문 크기, 배달 지역 고려하여 예상 배달 시간 계산

데이터

  • 메뉴 데이터
    • 이름
    • 가격
    • 설명
    • 재고 상태
  • 주문 데이터
    • 주문정보
      • 주문ID
      • 고객ID
      • 주문한 메뉴
      • 수량
      • 주문 시각
    • 결제 정보
      • 결제 방법
      • 결제 상태
      • 결제 금액
    • 배달 정보
      • 배달 주소
      • 예상 배달 시간
      • 배달 상태
  • 고객 데이터
    • 고객정보
      • 고객 ID
      • 이름
      • 연락처
      • 주소
  • 재고 데이터
    • 각 메뉴의 현재 재고 수량
  • 라이더 데이터
    • 라이더 정보
      • 라이더 ID
      • 이름
      • 현재 상태
        • 대기 중
        • 배달 중

5. Level 3

액션

  • 고객 인터페이스 관리
    • 고객의 웹사이트 접속
    • 고객에게 메뉴 표시
    • 고객에게 로그인 페이지 표시
    • 고객의 로그인 정보를 받고 검증
    • 고객의 메뉴 탐색
    • 고객의 메뉴 선택, 주문
    • 고객의 주문 내용 확인
    • 고객의 결제정보 입력
  • 주문 처리
    • 주문 정보 검증
    • 결제 처리
    • 주문 상태 업데이트
      • 접수 대기 중
      • 접수 완료
  • 주방 관리
    • 주방에 주문 내역 전달
    • 주문 상태 관리
      • 음식 조리 중
      • 라이더에게 전달 완료
  • 배달 관리
    • 준비된 음식 라이더에게 전달
    • 배달 상태 업데이트
      • 배달 중
      • 배달 완료

계산

  • 금액 계산
    • 각 메뉴의 가격과 선택된 수량에 따른 총금액 계산
    • 적용 가능한 할인 계산하여 최종 결제 금액 산출
  • 주문 검증
    • 메뉴 재고와 주문 수량 비교
    • 주문 가능 여부 결정
  • 배달 시간 계산
    • 주방의 현재 상태, 주문 크기, 배달 지역 고려하여 예상 배달 시간 계산
  • 로그인 유효성 검증
    • 입력된 로그인 정보의 유효성 검증
    • 로그인 성공 여부 결정

데이터

  • 메뉴 데이터
    • 이름
    • 가격
    • 설명
    • 재고 상태
  • 주문 데이터
    • 주문정보
      • 주문ID
      • 고객ID
      • 주문한 메뉴
      • 수량
      • 주문 시각
    • 결제 정보
      • 결제 방법
      • 결제 상태
      • 결제 금액
    • 배달 정보
      • 배달 주소
      • 예상 배달 시간
      • 배달 상태
  • 고객 데이터
    • 고객정보
      • 고객 ID
      • 이름
      • 연락처
      • 주소
    • 로그인 정보
      • 아이디
      • 비밀번호
      • 로그인 상태
  • 재고 데이터
    • 각 메뉴의 현재 재고 수량
  • 라이더 데이터
    • 라이더 정보
      • 라이더 ID
      • 이름
      • 현재 상태
        • 대기 중
        • 배달 중

6. 구현

이제 개발 환경을 설정하고 설계한 액션, 계산, 데이터를 구현해보겠습니다.

언어 : TypeScript

런타임 환경 : Node.js

프레임워크 및 라이브러리 : express.js

DB : MySQL

함수형 프로그래밍에서의 일반적인 구현순서

데이터 ➡️ 계산 ➡️ 액션

데이터

// 메뉴 데이터
type Menu = {
    name: string,
    price: number,
    description: string,
    stock: number
};

// 주문 데이터
type Order = {
    orderId: string,
    customerId: string,
    items: { menuId: string, quantity: number }[],
    orderTime: Date,
    paymentInfo: PaymentInfo,
    deliveryInfo: DeliveryInfo
};

// 결제 정보
type PaymentInfo = {
    method: string,
    status: 'pending' | 'completed' | 'failed',
    amount: number
};

// 배달 정보
type DeliveryInfo = {
    address: string,
    estimatedDeliveryTime: Date,
    status: 'preparing' | 'delivering' | 'delivered'
};

// 고객 데이터
type Customer = {
    customerId: string,
    name: string,
    contact: string,
    address: string,
    loginInfo: LoginInfo
};

// 로그인 정보
type LoginInfo = {
    username: string,
    password: string, hashed
    status: 'loggedOut' | 'loggedIn'
};

// 재고 데이터
type Stock = {
    menuId: string,
    quantity: number
};

// 라이더 데이터
type Rider = {
    riderId: string,
    name: string,
    status: 'waiting' | 'delivering'
};

계산

// 금액 계산
const calculateTotalAmount = (items: { menuId: string, quantity: number }[], menuData: Menu[]): number => {
    return items.reduce((total, item) => {
        const menu = menuData.find(menu => menu.name === item.menuId);
        return total + (menu ? menu.price * item.quantity : 0);
    }, 0);
};

// 주문 가능 여부 검증
const verifyOrder = (order: Order, stockData: Stock[]): boolean => {
    return order.items.every(item => {
        const stockItem = stockData.find(stock => stock.menuId === item.menuId);
        return stockItem ? stockItem.quantity >= item.quantity : false;
    });
};

// 배달 시간 계산 (예시, 실제 계산은 더 복잡할 수 있음)
const calculateDeliveryTime = (order: Order, currentStatus: string): Date => {
    const currentTime = new Date();
    // 예상 시간 계산 로직 (단순화)
    currentTime.setHours(currentTime.getHours() + 1); // 단순히 현재 시간에 1시간 추가
    return currentTime;
};

// 로그인 유효성 검증
const verifyLogin = (loginInfo: LoginInfo, customerData: Customer[]): boolean => {
    const customer = customerData.find(customer => customer.loginInfo.username === loginInfo.username);
    return customer ? customer.loginInfo.password === loginInfo.password : false;
};

액션

// 고객에게 메뉴 표시
app.get('/menu', (req: Request, res: Response) => {
    connection.query('SELECT * FROM Menu', (error: mysql.MysqlError, results: Menu[]) => {
        if (error) throw error;
        res.send(results);
    });
});

// 고객의 로그인 정보를 받고 검증
app.post('/login', (req: Request, res: Response) => {
    const { username, password } = req.body;
    connection.query('SELECT * FROM Customer WHERE username = ? AND password = ?', [username, password], (error: mysql.MysqlError, results: Customer[]) => {
        if (error) throw error;
        if (results.length > 0) {
            res.send({ status: 'success', message: 'Login successful' });
        } else {
            res.send({ status: 'fail', message: 'Login failed' });
        }
    });
});

// 주문 정보 검증 및 처리
app.post('/order', (req: Request, res: Response) => {
    const order: Order = req.body;
    // 주문 검증 로직 (예: 주문 가능 여부 확인, 금액 계산 등)
    // ...

    // 주문 정보를 데이터베이스에 저장
    connection.query('INSERT INTO Orders SET ?', order, (error: mysql.MysqlError, results: any) => {
        if (error) throw error;
        res.send({ status: 'success', message: 'Order placed successfully', orderId: results.insertId });
    });
});

// 주방에 주문 내역 전달
app.post('/order/:orderId/sendToKitchen', (req: Request, res: Response) => {
    const { orderId } = req.params;
    connection.query('UPDATE Orders SET status = "cooking" WHERE orderId = ?', [orderId], (error: mysql.MysqlError, results: any) => {
        if (error) throw error;
        res.send({ status: 'success', message: 'Order sent to kitchen successfully' });
    });
});

// 배달 상태 업데이트
app.post('/order/:orderId/updateDeliveryStatus', (req: Request, res: Response) => {
    const { orderId } = req.params;
    const { status } = req.body; // 예: "delivering" 또는 "delivered"
    connection.query('UPDATE Orders SET deliveryStatus = ? WHERE orderId = ?', [status, orderId], (error: mysql.MysqlError, results: any) => {
        if (error) throw error;
        res.send({ status: 'success', message: 'Delivery status updated successfully' });
    });
});

// ... 기타 필요한 액션 함수