Від насіння до розгортання: Повний посібник по створенню додатку Flower Shop 🚀

pic

Фото від Walls.io на Unsplash

Між роботою та особистим життям я витратив кілька годин тут і там на створення Flower Shop, простого веб-додатку, щоб вивчити різні аспекти розробки програмного забезпечення: від бекенд API до фронтенд фреймворків і хмарної інфраструктури. Хоча це особистий побічний проєкт, він відповідає багатьом принципам, які важливі для нас у Ferocia: чиста архітектура, ітеративна доставка та використання сучасних інструментів для створення надійних систем. Оскільки Ferocia має сильну інженерну культуру, яка підтримує безперервне навчання, я хочу поділитися своїм досвідом у створенні Flower Shop, включаючи технічні труднощі та уроки, які я виніс в процесі.

Натхнення для проєкту

Все почалося з двох речей, які я люблю: доставка програмного забезпечення та квіти 🌼. Я завжди знаходив радість у роботі на всіх етапах розробки, бачив, як моє рішення набуває форми від початку до кінця, і спостерігав, який вплив воно має на користувачів.

Також я хотів вивчити Ruby on Rails, оскільки Ferocia активно його використовує, і я не хотів бути єдиним, хто в кімнаті кидає кивок, а потім таємно шукає в Google 🥲. Я пробував онлайн-курси раніше, але ніколи не дотримувався їх до кінця. Кращий спосіб навчатися — це діяти, тому я вирішив побудувати щось веселе, корисне та водночас навчальне. І ось, з'явився Flower Shop app 🎊

Мета? Показати свою любов до квітів (ха-ха), практикуючи ітеративну доставку програмного забезпечення, розбиваючи роботу на маленькі, цінні частини, доставляючи їх рано, виявляючи ризики на початку та отримуючи відгуки в процесі. До того ж, хто не любить квіти? 😆

Хто повинен це прочитати?

Якщо вас цікавить:

  • Практика доставки програмного забезпечення (особливо ітеративна розробка та розробка з використанням tracer bullet)
  • Фронтенд-розробка з React та TypeScript
  • Бекенд-розробка з Ruby on Rails
  • Інфраструктура як код з Terraform та AWS
  • Pipeline як код з GitHub Actions

... тоді беріть чашку кави (або чаю) і давайте зануримось у це!

Насіння 🌱

Я люблю квіти та рослини, тому в моєму дворі багато рослин. Уявіть, що я вирішив запустити онлайн-магазин квітів для їх продажу. Оскільки я зайнятий і не маю багато ресурсів для інвестицій, я хотів швидко налаштувати щось, щоб поділитися з потенційними клієнтами. Першим кроком я уявив собі просту платформу для демонстрації красивих квітів і їх цін. Простий і без надмірностей!

“Посади насіння, піклуйся про нього і дозволь йому рости 🌱” — Май

Планування та ітеративний підхід

Коли я починав цей проєкт, я знав, що хочу слідувати ітеративному підходу розробки, доставляючи невеликі функціональні частини замість того, щоб будувати все одразу. Це дозволило б мені рано перевіряти припущення, виявляти ризики та швидко адаптуватися на основі того, що я дізнався.

Для Ітерації 1 я використовував підхід tracer bullet при створенні основи додатку Flower Shop з лише однією функцією — відображенням списку квітів — робочий потік від фронтенду до бекенду, аж до розгортання. Як постріл у темряву, цей підхід допомагає перевірити напрямок на ранньому етапі, наприклад, чи можу я використовувати Elastic Beanstalk для розгортання Rails-додатку, чи ні? Чи є невідомі фактори, з якими я можу зіткнутись зараз, а не пізніше? Це дозволяє робити постійні корективи замість того, щоб чекати до самого кінця, щоб побачити, чи працює все 🤩

Tracer bullet підхід у розробці програмного забезпечення — це техніка, при якій будується маленька функціональна частина системи від початку до кінця для перевірки припущень, виявлення ризиків на ранньому етапі та надання базового рівня для подальшої розробки.
Це схоже на постріл трасуючою кулею в темряві — хоча сам постріл може бути не зовсім точним, він допомагає визначити правильну траєкторію та необхідні коригування.

Ресурс: The Pragmatic Programmer: your journey to mastery, 20th Anniversary Edition, 2nd Edition

Ця ітерація була спрямована на те, щоб зробити базові функції робочими найпростішим і найшвидшим способом, забезпечуючи при цьому повну здатність до розгортання додатку.

pic

Додаток Flower Shop в дії

📚 Ось результат Ітерації 1: Перевірте додаток та код 🎊

Технічний стек

  • Фронтенд: React, Vite, TypeScript та повністю безсерверний, розміщений на AWS S3 — як букет, який чекає, щоб його помилували 🤣
  • Бекенд: Ruby on Rails (API), розгорнутий на AWS Elastic Beanstalk, який автоматично налаштовує EC2 інстанси.
  • Terraform керує інфраструктурою як код, забезпечуючи, що все можна відтворити та контролювати версії.
  • GitHub Actions автоматизує пайплайни розгортання для фронтенду та бекенду.

pic

Архітектура Flower Shop

Чому Elastic Beanstalk?

Я працював з AWS EC2, ECS і Lambda, але Elastic Beanstalk був для мене новою територією. Оскільки я хотів спростити досвід розгортання, зберігаючи контроль, я вирішив спробувати його. Elastic Beanstalk — це Platform-as-a-Service (PaaS), який обробляє розгортання, масштабування та моніторинг без необхідності керувати сировинними інстансами EC2. Все, що мені потрібно зробити, це завантажити код, і він все інше візьме на себе, при цьому я зберігаю гнучкість налаштовувати підключену інфраструктуру за необхідності. Найкраще з обох світів, напевно 😆

Кроки для створення Ітерації 1

Ось огляд високого рівня того, як я створив першу ітерацію додатку Flower Shop. Для більш детальної інформації перевірте README в репозиторії.

1. Налаштування монорепозиторію

Я структурував цей проєкт як монорепозиторій, зберігаючи фронтенд, бекенд і інфраструктуру в одному місці. Це дозволяє робити зміни по всій вертикалі (end-to-end) без необхідності перемикатися між різними репозиторіями.

flower-shop/  
│── flower-shop-frontend/ # React фронтенд (Vite + TypeScript)  
│ ├── src/  
│ ├── public/  
│ ├── infra/ # Terraform для інфраструктури фронтенду (S3, IAM)  
│ ├── package.json  
│ ├── tsconfig.json  
│ └── vite.config.ts  
│  
│── flower-shop-api/ # Rails бекенд (тільки API)  
│ ├── app/  
│ ├── config/  
│ ├── db/  
│ ├── infra/ # Terraform для інфраструктури бекенду (Elastic Beanstalk, IAM)  
│ ├── Gemfile  
│ ├── Rakefile  
│ ├── config.ru  
│ └── routes.rb  
│  
│── .github/ # CI/CD пайплайни  
│ ├── workflows/  
│ │ ├── deploy-frontend.yml  
│ │ ├── deploy-backend.yml  
│  
│── README.md  
│── .gitignore

Я почав з того, що створив нову папку для проєкту та ініціалізував git.

mkdir flower-shop  
cd flower-shop  
git init  
echo "# Flower Shop" > README.md  
git add .  
git commit -m "Initial commit"

2.

Будуємо фронтенд на React з Vite

Для фронтенду я вибрав Vite замість Create React App (CRA), оскільки:

  • Це забезпечує швидшу розробку завдяки миттєвій гарячій заміні модулів.
  • Забезпечує кращу продуктивність завдяки використанню ESBuild замість Webpack.
  • CRA не підходить для продуктивного використання і містить непотрібні залежності.

Цей крок налаштовує мінімальний React-додаток з TypeScript.

cd flower-shop-frontend  
npm install -g create-vite   
npm create vite@latest flower-shop-frontend --template react-ts

Щоб відобразити продукти на фронтенді, я створив компонент Products, який отримує список квітів з бекенд API і відображає їх. Я перевірив, що все працює за допомогою:

npm run dev

На цьому етапі я ще не стилизував компоненти, оскільки мета полягала в тому, щоб завершити роботу від початку до кінця якнайшвидше. Повернуся і оформлю їх пізніше (терпіть моє "перфекціонізм" 🥲). Зауваження: я використовую prettier як інструмент для форматування мого коду на React / TypeScript.

Я зробив коміт до main і перейшов до наступного кроку.

3. Створення Rails API та налаштування кінцевих точок

Бекенд — це Ruby on Rails API, який надає дані про квіткові продукти. Я створив кінцеву точку GET products/show endpoint, яка повертає список хардкодуваних продуктів.

rails new flower-shop-api --api   
cd flower-shop-api  
bundle install  
rails generate controller products show

Змініть config/routes.rb:

Rails.application.routes.draw do  
 get "products/show"  
end

Змініть app/controllers/products_controller.rb:

class ProductsController < ApplicationController  
 def show  
 render json: [  
 {  
 "name": "Sakura",  
 "imageUrl": "https://abc.com/sakura.jpg",  
 "price": 15.00  
 }  
 ]  
 end  
end

Я перевірив, що все працює, спочатку запустив сервер, а потім використав Insomnia для запиту до кінцевої точки http://localhost:3000/products/show. Можна також використовувати curl, браузер або будь-який інший інструмент, що вам зручний.

rails s

Перед тим як зробити коміт, я спробував налаштувати форматувальник для Ruby. Здавалося, що це проста задача, але вона перетворилася на справжнє випробування, коли я намагався налаштувати Rubocop, SyntaxTree, Standard Ruby для роботи з VS Code. Врешті-решт, я змусив працювати Standard Ruby після години боротьби 😭 Експерти Ruby! Який форматувальник ви використовуєте?

4. Налаштування інфраструктури фронтенду

Щоб забезпечити відтворюваність та масштабованість інфраструктури (і, більш важливо, тому що я лінивий 😝), я використав Terraform для налаштування S3 бакету для хостингу фронтенду. Я також не забув дати публічний доступ до бакету, щоб люди з Інтернету могли доступити додаток.

Примітка: Якщо у вас ще немає акаунта AWS, створіть його. AWS пропонує безкоштовний пробний період! Під час налаштування awscli, уникайте використання root-користувача. Root-користувач має необмежений доступ до всіх ресурсів, і його слід використовувати тільки для критичних дій на рівні акаунта. Натомість використовуйте IAM користувача або роль і надавайте їм відповідні дозволи, дотримуючись принципу найменших привілеїв. Дивіться README для докладних інструкцій.

Перейдіть до flower-shop-frontend і створіть папку infra для всіх ресурсів інфраструктури. Я почав з одного файлу main.tf для всіх ресурсів, необхідних для фронтенду на даний момент.
Я почну розділяти різні ресурси на різні файли, оскільки вони будуть розширюватися.

resource "aws_s3_bucket" "frontend_bucket" {  
 bucket = "flower-shop-frontend"  

 tags = {  
 Name = "Flower Shop Frontend"  
 }  
}  

resource "aws_s3_bucket_website_configuration" "frontend_config" {  
 bucket = aws_s3_bucket.frontend_bucket.id  

 index_document {  
 suffix = "index.html"  
 }  

 error_document {  
 key = "index.html"  
 }  
}  

resource "aws_s3_bucket_public_access_block" "frontend_public_access" {  
 bucket = aws_s3_bucket.frontend_bucket.id  

 block_public_acls = false  
 block_public_policy = false  
 ignore_public_acls = false  
 restrict_public_buckets = false  
}  

resource "aws_s3_bucket_policy" "frontend_policy" {  
 bucket = aws_s3_bucket.frontend_bucket.id  
 policy = <  

Налаштування CI/CD пайплайна з GitHub Actions

Я автоматизував деплойменти за допомогою **GitHub Actions**, тож кожен пуш у гілку `main` запускає збірку, тестування та деплой обох частин — фронтенду та бекенду.

> **GitHub Actions** — це інструмент автоматизації CI/CD, вбудований у GitHub, який дозволяє визначати та запускати робочі процеси для зборки, тестування та деплойменту коду. Він використовує конфігураційні файли на основі YAML для автоматизації завдань, таких як запуск тестів, лінтинг коду та деплоймент додатків. Робочі процеси запускаються подіями, такими як пуші коду, pull-запити чи заплановані завдання. GitHub Actions безкоштовний для публічних репозиторіїв.
> 
> Ресурс: [GitHub Actions](https://github.com/features/actions)

Щоб налаштувати робочі процеси, перейдіть до **кореневої директорії** та створіть папку `.github/workflows/`. Я створив різні робочі процеси для фронтенду та бекенду, щоб вони могли виконуватись одночасно.

Робочий процес **deploy-frontend** збирає React-додаток та завантажує статичні файли до S3 бакету, який хостить фронтенд.

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

  • name: Install Node.js
    uses: actions/setup-node@v4
    with:
    node-version: 20

  • name: Cache Node.js modules
    uses: actions/cache@v3
    with:
    path: flower-shop-frontend/node_modules
    key: ${{ runner.os }}-node-modules-${{ hashFiles('flower-shop-frontend/package-lock.json') }}
    restore-keys: |
    ${{ runner.os }}-node-modules-

  • name: Install dependencies
    working-directory: flower-shop-frontend
    run: npm install

  • name: Run Frontend Tests (Vitest)
    working-directory: flower-shop-frontend
    run: |
    npm test

  • name: Build frontend
    working-directory: flower-shop-frontend
    run: npm run build

  • name: Deploy to S3
    run: aws s3 sync flower-shop-frontend/dist/ s3://flower-shop-frontend --delete
    env:
    AWSACCESSKEYID: ${{ secrets.AWSACCESSKEYID }}
    AWSSECRETACCESSKEY: ${{ secrets.AWSSECRETACCESSKEY }}
    AWS_REGION: ap-southeast-2
    ```

Після того як я зробив цей коміт, я зміг побачити новостворений робочий процес в дії, перейшовши до репозиторію в GitHub і натиснувши на Actions (див. нижче).

pic

Робочий процес GitHub Actions для деплойменту фронтенду

Робочий процес deploy-backend автоматизує деплоймент на AWS Elastic Beanstalk. Кожен пуш триггерить процес деплойменту, який оновлює Rails API, що працює в середовищі Elastic Beanstalk, гарантуючи, що нові зміни будуть доступні швидко.

name: Deploy Backend  

on:  
 push:  
 branches:  
 - main  

jobs:  
 deploy:  
 name: Deploy to Elastic Beanstalk  
 runs-on: ubuntu-latest  

 env:  
 AWS_REGION: ap-southeast-2  
 EB_ENV: flower-shop-api-env  
 EB_APP: flower-shop-api  
 EB_PLATFORM: "Ruby"  

 steps:  
 - name: Checkout Repository  
 uses: actions/checkout@v4  

 - name: Set up Ruby and Bundler  
 uses: ruby/setup-ruby@v1  
 with:  
 ruby-version: 3.3  
 bundler-cache: true  

 - name: Install Dependencies (Backend)  
 working-directory: flower-shop-api  
 run: |  
 bundle install  

 - name: Run Backend Tests (RSpec)  
 working-directory: flower-shop-api  
 run: |  
 bundle exec rspec  

 - name: Set up AWS CLI  
 uses: aws-actions/configure-aws-credentials@v2  
 with:  
 aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}  
 aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}  
 aws-region: ${{ env.AWS_REGION }}  

 - name: Install Elastic Beanstalk CLI  
 run: |  
 pip install --upgrade awsebcli  
 eb --version  

 - name: Initialize Elastic Beanstalk (if not already set up)  
 working-directory: flower-shop-api  
 run: |  
 if ! eb status $EB_ENV 2>/dev/null; then  
 eb init -p "$EB_PLATFORM" $EB_APP --region $AWS_REGION   
 eb use $EB_ENV  
 fi  

 - name: Deploy to Elastic Beanstalk  
 working-directory: flower-shop-api  
 run: |  
 eb deploy $EB_ENV

Налагодження

… І я думав, що все зроблено, і мій додаток Flower Shop розцвів. Але НІ 😭. Деплоймент Rails додатку на Elastic Beanstalk не був таким простим. Ось кілька проблем, з якими я зіткнувся.

- **EC2 обчислювальні ресурси:** Я деплоїв свій Rails додаток на EC2 інстанс `t3.micro`, але деплой не проходив. Виявилося, що `t3.micro` не має достатньо ресурсів для коректної роботи Rails додатку. Після деяких налаштувань я оновив інстанс до `t3.small`, що вирішило проблему. Мене це змусило задуматися, чому Rails додаток потребує так багато обчислювальних потужностей? Для NodeJs та .Net це було нормально 🧐
- **Проблема зі здоров'ям інстанса:** Спочатку Elastic Beanstalk відзначав додаток як нездоровий через відсутність ендпоінта за замовчуванням для перевірки стану. Я вирішив це, додавши простий ендпоінт `/up` в Rails і вказав його в Terraform файлі `main.tf`.
- **Відсутній Rake:** Деплоймент на Elastic Beanstalk не проходив через відсутність гемів `Rake`, хоча я не використовував його безпосередньо у своєму Rails додатку. Виявилося, що Rake є важливою частиною екосистеми Rails. Наприклад, Elastic Beanstalk використовує Rake для певних хуків життєвого циклу, таких як компіляція активів або інші фонова налаштування. Без нього процес деплойменту призводив до несподіваних помилок. Я вирішив це, додавши `Rake` до свого Gemfile та забезпечив його встановлення до деплойменту.
- **Невідповідність версії Ruby:** Моя локальна версія Ruby не відповідала версії у Elastic Beanstalk, що призводило до непередбачуваної поведінки. Я вирівняв рішення у Elastic Beanstalk в файлі `main.tf`, щоб відповідати моєму локальному середовищу і уникнути проблем сумісності.
- **База даних:** Для Rails додатку потрібна була налаштована база даних, але я не потребував її для цієї ітерації. Я не зміг її видалити або встановити в `null`, тому налаштував її на використання вбудованої бази даних в пам'яті, встановивши `database: ":memory:"` в `config/database.yml`.
- **Відсутній секретний ключ:** Я забув інжектувати `SECRET_KEY_BASE` в Elastic Beanstalk, через що Rails додаток не запускався в продакшн середовищі. Але чому це було потрібно? 🤔 `SECRET_KEY_BASE` — це важлива частина безпекової інфраструктури Rails, використовується для підписування cookie, шифрування сесійних даних та забезпечення цілісності даних. Без нього Rails відмовляється завантажуватися в продакшн-режимі як заходи безпеки. Я пізніше налаштував це через Terraform, щоб забезпечити безпеку роботи додатку через **GitHub Actions Secrets and Variables**.
- **Проблеми з CORS:** Фронтенд не міг отримувати дані від бекенду через обмеження CORS. Я виправив це, налаштувавши middleware в Rails для дозволу міждоменної роботи в `cors.rb`.
- **Проблеми з SSL:** Однією з несподіванок став issue з SSL. Я спочатку намагався увімкнути SSL, але це виявилося більш складним, ніж я очікував, тому наразі я вимкнув його. Це означає, що користувачі побачать попередження "Не безпечно" в браузері при доступі до сайту. Не ідеально, але ми виправимо це в ітерації 2, забезпечивши належний рівень безпеки.
- **Журнали та налагодження:** Знайти журнали було не так просто, як я очікував. Команда `eb logs` допомогла, але я також змушений був підключитися до інстанса через SSH, щоб налагодити деякі проблеми 😅

## Рефлексія по Ітерації 1

Яка подорож! Спочатку все йшло добре. Фронтенд? Перевірено. Бекенд? Перевірено. Terraform? Перевірено. Все це було зроблено за 2 години. Але потім Elastic Beanstalk почав підносити сюрпризи. Виявляється, **деплоймент Rails** — це не просто простий "push-and-go". Замість цього я опинився в павутині залежностей, загадок перевірки стану та драми з CORS на понад 3 години 😱. І не починайте зі мною про форматери Ruby у VS Code, на які я витратив більше години. Я витратив занадто багато часу, намагаючись їх налаштувати.

Але ось, після шести годин боротьби з конфігураціями, налагодженням журналів і безліччю запитів у Google, **я отримав додаток**! Можливо, це здається не таким вже великим досягненням, але це основа для всього, що буде далі.
Мій маленький [Flower Shop додаток](http://flower-shop-frontend.s3-website-ap-southeast-2.amazonaws.com/) живий, безперервно розгорнутий та готовий до розвитку 🌼

## Що далі

Із завершенням Ітерації 1, наступний крок — покращення додатку шляхом додавання ключових функцій та вдосконалення загальної функціональності. Ось що планується в майбутніх ітераціях:

- **SSL (HTTPS):** Забезпечити додаток SSL сертифікатом та прибрати попередження "Не безпечно" в браузері.
- **Інтеграція з базою даних:** Перехід від хардкодених даних продуктів до реальної бази даних (можливо, PostgreSQL 🤔).
- **Поліпшення UI/UX:** Покращити досвід користувачів завдяки кращому стилю та інтерактивним можливостям.
- **Аутентифікація:** Реалізація користувацьких акаунтів та функціональності для входу.
- **Авторизація:** Реалізація авторизації між React додатком та Rails API.
- **Інтеграційне тестування та тестування користувацьких шляхів:** Розширення тестування за межі юніт-тестів.
- **Моніторинг та спостережуваність:** Налаштування журналювання, відстеження продуктивності та моніторинг помилок.
- **Docker для деплойменту:** Для простішої портативності та масштабованості.
- **Автоматизація налаштування інфраструктури** в пайплайні.

Очікуйте **Ітерацію 2**, де додаток Flower Shop розквітне ще більше 🌸🌸🌸

📚 [Перегляньте додаток](http://flower-shop-frontend.s3-website-ap-southeast-2.amazonaws.com/) та [код](https://github.com/maiisthebest/flower-shop/tree/iteration-1).



Перекладено з: [From Seed to Deployment: A Full-Stack Guide to the Flower Shop App 🚀](https://medium.com/ferocia-engineering/from-seed-to-deployment-a-full-stack-guide-to-the-flower-shop-app-231e22712f75)