├── hello.py ├── mvc-with-service.png ├── 12-Factor-app-revised.jpg ├── LICENSE ├── EN.md └── README.md /hello.py: -------------------------------------------------------------------------------- 1 | print('Hello, world!') 2 | -------------------------------------------------------------------------------- /mvc-with-service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beagreatengineer/how-to-develop-perfect-crud/HEAD/mvc-with-service.png -------------------------------------------------------------------------------- /12-Factor-app-revised.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beagreatengineer/how-to-develop-perfect-crud/HEAD/12-Factor-app-revised.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Eugene Kozlov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /EN.md: -------------------------------------------------------------------------------- 1 | Translations: [EN(you are here)](EN.md), [RU](README.md) 2 | # 🤔 What is this? 3 | This article is an attempt to combine good practices that are useful to keep in mind and apply when developing a backend application. 4 | 5 | It can be used as a checklist if: 6 | - You are starting a greenfield project and want to ensure that best practices are used right from the start. 7 | - You received a test assignment and have decided to move forward with it. 8 | 9 | ## 🤔 Does this article only cover backend apps? 10 | Because the author's expertise lies mostly on the backend side, most suggestions will be useful for developing backend apps. However, a lot of the points covered in this article can also be handy for other types of development. 11 | 12 | ## 🤔 There is a lot in here, do I really need all of this? 13 | 14 | The more features and practices you adopt in your project, the better. Applying all of them at once would be hard so consider your time 🕒 and effort 💪 to prioritize specific practices. 15 | ## 🤔 "This article is missing information about technology X" 16 | 17 | If you have a proposal about covering a specific technology here, don't be shy: 18 | - Open an issue ✅ 19 | - Open a PR ✅ 20 | 21 | # 📖 Table of Contents 22 | 23 | - [Repository](#repository) 24 | - [Code Style](#code-style) 25 | - [✔️Tests](#%EF%B8%8Ftests) 26 | - [⚙️Configuration & Infrastructure around Code](#%EF%B8%8Fconfiguration--infrastructure-around-code) 27 | - [API Design](#api-design) 28 | - [Authorization & Authentication](#authorization--authentication) 29 | - [MVC Explanation](#mvc-explanation) 30 | - [Controller](#controller) 31 | - [Model](#model) 32 | - [Service](#service) 33 | - [View](#view) 34 | - [📐✏️👷‍♀️Architecture, Design Patterns, Refactoring, etc](#%EF%B8%8F%EF%B8%8Farchitecture-design-patterns-refactoring-etc) 35 | - [🔒CRUD: Validations](#crud-validations) 36 | - [CRUD: Database](#crud-database) 37 | - [CRUD: Operations](#crud-operations) 38 | - [LIST (HTTP GET)](#list-http-get) 39 | - [READ (HTTP GET)](#read-http-get) 40 | - [CREATE (HTTP POST)](#create-http-post) 41 | - [UPDATE (HTTP PUT/PATCH)](#update-http-putpatch) 42 | - [DESTROY (HTTP DELETE)](#destroy-http-delete) 43 | - [External API Calls, Long-running tasks (And why we need message queue)](#external-api-calls-long-running-tasks-and-why-we-need-message-queue) 44 | - [📈Logs and Metrics](#logs-and-metrics) 45 | - [🛡️Security](#%EF%B8%8Fsecurity) 46 | - [CORS Headers (Cross-Origin Resource Sharing)](#cors-headers-cross-origin-resource-sharing) 47 | - [Cache](#cache) 48 | - [WIP: Transactions, Locks, Isolation Levels, ACID](#wip-transactions-locks-isolation-levels-acid) 49 | - [WIP: Full Text Search](#wip-full-text-search) 50 | 51 | # Repository 52 | * Codebase has to live on a public or private Git repository (Github / Gitlab / Bitbucket) 53 | * Repository settings must disable force pushing into (`push --force`) into main branches (`master`, `main`, release branches) 54 | * 'README' should contain: 55 | - info about the project 56 | - short summary of tooling and the tech stack 57 | - instructions for setting up and launching the application 58 | * Use feature branches, pull requests. Refer to this great [article](https://www.flagship.io/git-branching-strategies/) with comparison of different Git Branching Strategies. 59 | * Use readable commit history. Here is a good set of rules [Conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) 60 | * Set up Continuous Integration (Gitlab CI / Github Actions) 61 | * `feature/` and master branches should be set up with: 62 | * test runners and [coverage](https://www.guru99.com/test-coverage-in-software-testing.html) stats 63 | * linting 64 | * Setting up Continuous Delivery - app deployment into different environments (e.g. test/staging/prod) would be a huge plus! 65 | * Optional: Set up [dependabot](https://docs.github.com/ru/code-security/dependabot/working-with-dependabot) 66 | 67 | # Code Style 68 | Before development: 69 | * Have a working editor or an IDE: 70 | * VS Code 71 | * Visual Studio 72 | * PyCharm 73 | * IDEA 74 | * Vim, emacs 75 | * [EditorConfig](https://editorconfig.org/) plugin / add-on is up and running for your editor 76 | * Set up Code formatters: 77 | * Rubocop for Ruby 78 | * Pylint/Black/PEP8 for Python 79 | # ✔️Tests 80 | * Install libraries for writing various test types (unit, integration). For instance: 81 | * Pytest for Python 82 | * RSpec for Ruby 83 | * Testify, testcontainers for Golang 84 | * Test coverage is calculated automatically after each test run 85 | * Apply [AAA (Arrange Act Assert)](https://medium.com/@pjbgf/title-testing-code-ocd-and-the-aaa-pattern-df453975ab80) pattern when writing unit tests 86 | 87 | Try to adhere to the [testing pyramid] (https://martinfowler.com/articles/practical-test-pyramid.html) principle. Note that different test types require different tools. For end-to-end (e2e) testing you can try using [Selenium](https://www.selenium.dev/) or [Cypress](https://www.cypress.io/how-it-works/). Integration tests can be added via `testcontainers`. 88 | 89 | # ⚙️Configuration & Infrastructure around Code 90 | * Have `docker` and `docker-compose` installed on your local machine 91 | * There is a Dockerfile in your repository which can be used to build your app into a `Docker container` 92 | * [Best practices when writing a Dockerfile for a Ruby application](https://lipanski.com/posts/dockerfile-ruby-best-practices) (although, these tips can be useful for other languages) 93 | * [Google Cloud: Best practices for building containers](https://cloud.google.com/architecture/best-practices-for-building-containers) 94 | * All app dependencies are listed in a `docker-compose.yml` file 95 | * Setting up and launching your application should be as easy and straightforward. It is possible that you may need to write some additional `base/zsh/powershell` scripts 96 | * [Your application should have multiple environments (development, prod, test)](https://12factor.net/dev-prod-parity) 97 | * Use a suitable application server for your `production` environment. For example: 98 | * Puma for Ruby 99 | * Gunicorn3 for Python 100 | * Undertow for Java 101 | 102 | When writing the configuration for your application, use the 12factor principles. The diagram below is copied from the [12 Factor App Revisited](https://architecturenotes.co/12-factor-app-revisited/) article 103 | 104 | 105 | # API Design 106 | * Use the REST](https://www.freecodecamp.org/news/rest-api-best-practices-rest-endpoint-design-examples/) architecture conventions as your guiding principle for naming paths, types of operations and API response statuses. 107 | * Response type: JSON (unless otherwise specified) 108 | * There is an option to open Swagger in your repository to familiarize the next developer with your API design 109 | * It can be written by hand 110 | * Or you can use a codegen: [rswag (Rails)](https://github.com/rswag/rswag), [safrs (Flask)](https://github.com/thomaxxl/safrs), [echo-swagger (Echo/Golang)](https://github.com/swaggo/echo-swagger) 111 | 112 | If you think that REST+JSON option is not a great fit for your application design, or your task requires using a different format, it could be beneficial to familiarize yourself with some alternatives: 113 | - [SOAP](https://www.w3.org/TR/soap12-part1/) 114 | - [JSON-RPC](https://www.jsonrpc.org/) 115 | - [gRPC](https://grpc.io/) 116 | - [GraphQL](https://graphql.org/) 117 | 118 | # Authorization & Authentication 119 | **Authentication** - is a process of verifying a user identity. The most common way to authenticate a user is to compare the password they entered with the password saved in the database. 120 | 121 | The following strategies can be used to authenticate API users: 122 | * HTTP Basic Auth (easy) 123 | * JSON Web Tokens (a bit more complex) 124 | 125 | **Authorization** - granting a particular user the rights for performing specific actions. 126 | For instance: a user who was banned by admin cannot post comments (even though they have been successfully authenticated). 127 | 128 | Some examples of libraries: 129 | - [Pundit for Ruby](https://github.com/varvet/pundit) 130 | - [Casbin (Many languages supported)](https://github.com/casbin/casbin) 131 | 132 | Additional links: 133 | - [Difference between RBAC vs. ABAC vs. ACL vs. PBAC vs. DAC](https://www.strongdm.com/blog/rbac-vs-abac) 134 | 135 | # MVC Explanation 136 | Goal: split the responsibilities between components. MVC is a type of architecture that allows a developer to build applications without excessive cognitive load (compared to other web architecture types) 137 | 138 | 139 | 140 | ## Controller 141 | - Accepts the request and validates request body based on rules set within your API 142 | - Checks authorization + authentication 143 | - Calls a Service and injects data into it 144 | - Based on Service return, a controller formats a response and passes it into a View 145 | ## Model 146 | * Reflects basic description of schema and relations with other models 147 | * Contains minimum business logic, or ideally none at all 148 | * Is used to make requests to the DB for reads and writes 149 | ## Service 150 | * Accepts arguments from controllers and validates the request body 151 | * Uses a Model for reading and writing data to DB. 152 | * Is responsible for the business logic of your application 153 | ## View 154 | * Builds an API response based on data passed to it 155 | 156 | # 📐✏️👷‍♀️Architecture, Design Patterns, Refactoring, etc 157 | After grasping the concept of MVC, try developing a deeper understanding and study: 158 | - different app architecture approaches 159 | - patterns that can make your code future-proof 160 | 161 | A few courses that I recommend: 162 | - [Categorized overview of programming principles & design patterns](https://github.com/webpro/programming-principles) 163 | - [Refactoring Patterns and Design Patters Reference](https://refactoring.guru) 164 | - [Summary of "Clean code" by Robert C. Martin](https://gist.github.com/wojteklu/73c6914cc446146b8b533c0988cf8d29) 165 | - [Summary of "Clean Architecture" by Robert C. Martin](https://gist.github.com/ygrenzinger/14812a56b9221c9feca0b3621518635b) 166 | 167 | # 🔒CRUD: Validations 168 | Before persisting data in the database, one should: 169 | - validate the types (e.g. rows that expect string data types receive string data etc.) 170 | - ensure API request body consistency (if a request contains fields that do not have matching columns in the database, these fields should be ignored) 171 | # CRUD: Database 172 | * Use and ORM (or a similar tool), unless your requirements specify using pure SQL. 173 | * Easier to operate 174 | * Safe, because most ORMs offer protection against common SQL vulnerabilities out of the box. 175 | * Use migrations to create tables and other entities in your DB (Rails Migrations, Flask-Migrate, etc) 176 | * When describing tables, it is important to specify required constraints (NULLABLE, DEFAULT VALUE, UNIQUE, PRIMARY KEY) 177 | * When describing tables, it is important to specify indices for columns that are expected to be indexed. 178 | * To protect an API from sequencing attacks, you can try using a `uuid` instead of a `serial` 179 | 180 | 181 | P.S. Try following the principle of [strong migrations](https://github.com/ankane/strong_migrations) to avoid blocking the DB. 182 | # CRUD: Operations 183 | ## LIST (HTTP GET) 184 | * A response should include ID(s) for every resource. 185 | * Resources should be sorted by some type of attribute such as date of creation. 186 | * An API should support pagination to avoid returning all resources at one. [Database pagination techniques](https://dev.to/appwrite/this-is-why-you-should-use-cursor-pagination-4nh5) 187 | * The number of requests to the database within a single API request must be limited avoiding [the N+1 queries problem](https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping) 188 | 189 | An API should not return all the fields for a model. 190 | Example of a response for a list of articles: 191 | * ID 192 | * Title 193 | * Author name 194 | * First few sentences of the body 195 | 196 | Sending a full text body is really unnecessary. 197 | 198 | ## READ (HTTP GET) 199 | * Returning resources with all of their fields. Nothing special here. 200 | ## CREATE (HTTP POST) 201 | * Filter out fields that should not be accessed by users. 202 | * Commit the INSERT in our DB. 203 | * Send a response with our newly-created resource ID and content. 204 | 205 | Bonus points: 206 | - Make sure that the API endpoint is [idempotent](https://restfulapi.net/idempotent-rest-apis/) 207 | - Set up a rate limiter to protect the database from spam 208 | ## UPDATE (HTTP PUT/PATCH) 209 | * Know the difference between PUT and PATCH HTTP methods 210 | * Filter out fields that should not be accessed by users. 211 | * User authorization check 212 | * Example: users should not be allowed to edit other users' comments. 213 | * Commit the update depending upon the selected HTTP method. 214 | 215 | ## DESTROY (HTTP DELETE) 216 | * Commit deletion after checking the existing of resource in the database and user authorization 217 | 218 | Additionally, you might considering implementing "soft-deletion" (hide deleted resources from users, but keep them in the DB) 219 | # External API Calls, Long-running tasks (And why we need message queue) 220 | If an API needs to: 221 | - send requests to third-party resources 222 | - generate reports or perform long requests to DB 223 | then it might be a good idea to perform these operations outside of HTTP requests. 224 | 225 | To do these operations, we will need a Queue, to which we'll add or remove Tasks. 226 | 227 | Examples of high-level libraries that solve the problem of scheduling, reading and processing tasks: 228 | * Celery for Python (tasks are stored in `Redis`) 229 | * Sidekiq for Ruby (tasks are stored in `Redis`) 230 | 231 | It is important to note that Redis is not the only option and it might not be suitable for all types of applications. 232 | Hence, it would be a good idea to learn at least two other advanced libraries for storing and processing queues - `RabbitMG` an d 'Kafka' 233 | 234 | Additional links: 235 | - [Latency, throughput, and availability: system design interview concepts](https://igotanoffer.com/blogs/tech/latency-throughput-availability-system-design-interview) - A more detailed explanation of why fast processing HTTP requests is so important. 236 | 237 | # 📈Logs and Metrics 238 | 239 | Metrics: 240 | 241 | Set up Prometheus metrick with data about the state of HTTP API and application runtime. We recommend using settings that collect application performance metrics using RED (Rate Error Duration)](https://www.infoworld.com/article/3638693/the-red-method-a-new-strategy-for-monitoring-microservices.html) and [USE (Utilization Saturation Errors)](https://www.brendangregg.com/usemethod.html) methodologies: 242 | - [prometheus, promauto, promhttp for Go](https://prometheus.io/docs/guides/go-application/) 243 | - [starlette-prometheus for Python](https://github.com/perdy/starlette-prometheus) 244 | 245 | 246 | Logs: 247 | - [Logs should only be sent to stdout](https://12factor.net/logs) 248 | - [Logs should have a strict format like JSON](https://coralogix.com/blog/json-logging-why-how-what-tips/) 249 | 250 | 251 | # 🛡️Security 252 | - Check library versions you are using for known vulnerabilities. Some utilities that can audit your libraries are: 253 | - [bundler-audit for Ruby](https://github.com/rubysec/bundler-audit) 254 | - [pip-audit for Python](https://pypi.org/project/pip-audit/) 255 | - [local-php-security-checker for PHP](https://github.com/fabpot/local-php-security-checker) or the `symfony check:security` command, if you are using [Symfony](https://symfony.com/) framework 256 | - Set up dependabot to check and update library versions 257 | - Ensure that the app is protected against common vulnerabilities - [OWASP TOP 10](https://owasp.org/www-project-top-ten/). Here is a tool that can help with this difficult task [checklist №1](https://github.com/shieldfy/API-Security-Checklist) and [№2 (with examples for Ruby on Rails)](https://github.com/brunofacca/zen-rails-security-checklist) 258 | # CORS Headers (Cross-Origin Resource Sharing) 259 | 260 | When dealing with modern single page applications written in popular frameworks like React, Vue.js or Angular, you are likely to encounter a scenario where a client domain will be different from your API domain. In this case, your API will have to include CORS headers to let the browser know that responses coming from the API are permitted. 261 | 262 | Typically, a CORS module is already available in your framework of choice and you can use its defaults. If you need more a more granular configuration, here is a few libraries that can help: 263 | - [Go Echo](https://echo.labstack.com/middleware/cors/) 264 | - [Javascript ExpressJS](https://expressjs.com/en/resources/middleware/cors.html) 265 | 266 | You can learn more about CORS headers here [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). 267 | 268 | # 🚄Cache 269 | First, try to ask yourself 270 | - "Why do I need it? What problem is it going to solve?" 271 | - "Could I do without it?" (for example, by updating the DB schema, adding indices or optimizing etc) 272 | 273 | If you are set on implementing caching, then you'll need to: 274 | - Choose the [cache invalidation strategy](https://codeahoy.com/2017/08/11/caching-strategies-and-how-to-choose-the-right-one/) based on: 275 | - app business logic 276 | - load profiling (read heavy, write heavy) 277 | - available resources (implementation of certain strategies can be really complex) 278 | - Choose the size and policy for evicting data (because it is impossible to store all of your data in cache). The most popular choice here is Least Recently Used (LRU). 279 | - Choose the storage: 280 | - RAM (in app instances) 281 | - DB (Redis, Memcached, Dynamo, etc) 282 | - Identify the key metrics for measuring the effectiveness (cache hit rate), and be able to change the size or strategy if needed. 283 | 284 | # WIP: Transactions, Locks, Isolation Levels, ACID 285 | # WIP: Full Text Search 286 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](https://t.me/careerunderhood) [](https://t.me/ea_kozlov) 2 | 3 | Translations: [EN](EN.md), [RU(you are here)](README.md) 4 | 5 | # 🤔 Что это? 6 | Данная статья - попытка объединить в одном месте хорошие практики, которые полезно знать и применять при разработке бэкэнд приложения. 7 | 8 | Её можно использовать как полноценный чеклист, который будет полезен, если Вы: 9 | - Начинаете новый проект с нуля и сразу хотите заручиться набором хороших практик. 10 | - Получили тестовое задание и всерьез настроены реализовать его. 11 | 12 | 13 | ## 🤔 Здесь только про Backend? 14 | Так как автор имеет экспертизу только в Backend то и большинство советов будут полезны при разработке Backend приложений. Впрочем многие из советов будут полезны и для других специальностей. 15 | ## 🤔 Слишком много всего, это точно нужно? 16 | Чем больше фич и практик вы реализуете в рамках проекта тем качественнее будет результат. Но реализовать все и сразу может быть сложно и долго, рассчитайте время 🕒 и силы 💪. 17 | ## 🤔 "Здесь не хватает информации о технологии X" 18 | Если у вас есть предложения по наполнению репозитория, не стесняйтесь: 19 | - Предлагать идеи через issues ✅ 20 | - Предлагать конкретные изменения через PR ✅ 21 | 22 | 23 | # 📖 Table of Contents 24 | 25 | - [Repository](#repository) 26 | - [Code Style](#code-style) 27 | - [✔️Tests](#%EF%B8%8Ftests) 28 | - [⚙️Configuration & Infrastructure around Code](#%EF%B8%8Fconfiguration--infrastructure-around-code) 29 | - [🚀Deployment](#deployment) 30 | - [Level 0: Use free hostings](#level-0-use-free-hostings) 31 | - [Level 1: SSH, Git, VPS / Cloud (EC2) (without Docker)](#level-1-ssh-git-vps--cloud-ec2-without-docker) 32 | - [Level 2: Containers and VPS (Docker required)](#level-2-containers-and-vps-docker-required) 33 | - [Level 3: Containers and clouds (Docker required)](#level-3-containers-and-clouds-docker-required) 34 | - [Level 4: Containers and Orchestrators (Docker required)](#level-4-containers-and-orchestrators-docker-required) 35 | - [Bonus #1: Learn deployment strategies](#bonus-1-learn-deployment-strategies) 36 | - [Bonus #2: Shutdown app gracefully with zero downtime](#bonus-2-shutdown-app-gracefully-with-zero-downtime) 37 | - [API Design](#api-design) 38 | - [🧱Authorization & Authentication](#authorization--authentication) 39 | - [MVC Explanation](#mvc-explanation) 40 | - [Controller](#controller) 41 | - [Model](#model) 42 | - [Service](#service) 43 | - [View](#view) 44 | - [📐✏️👷‍♀️Architecture, Design Patterns, Refactoring, etc](#%EF%B8%8F%EF%B8%8Farchitecture-design-patterns-refactoring-etc) 45 | - [🔒CRUD: Validations](#crud-validations) 46 | - [CRUD: Database](#crud-database) 47 | - [CRUD: Operations](#crud-operations) 48 | - [LIST (HTTP GET)](#list-http-get) 49 | - [READ (HTTP GET)](#read-http-get) 50 | - [CREATE (HTTP POST)](#create-http-post) 51 | - [UPDATE (HTTP PUT/PATCH)](#update-http-putpatch) 52 | - [DESTROY (HTTP DELETE)](#destroy-http-delete) 53 | - [External API Calls, Long-running tasks (And why we need message queue)](#external-api-calls-long-running-tasks-and-why-we-need-message-queue) 54 | - [Logs](#logs) 55 | - [Metrics](#metrics) 56 | - [🛡️Security](#%EF%B8%8Fsecurity) 57 | - [CORS Headers (Cross-Origin Resource Sharing)](#cors-headers-cross-origin-resource-sharing) 58 | - [🚄Cache](#cache) 59 | - [Remote Config / Feature Toggles / Feature Flags](#remote-config--feature-toggles--feature-flags) 60 | - [Transactions, Locks, Isolation Levels, ACID](#transactions-locks-isolation-levels-acid) 61 | - [WIP: Full Text Search](#wip-full-text-search) 62 | 63 | 64 | 65 | # Repository 66 | * Код должен храниться в публичном/приватном Git репозитории (Github / Gitlab / Bitbucket) 67 | * В Git-репозитории должен быть запрещен push с флагом `--force` в основные ветки (`master`, `main`, релизные ветки). 68 | * `README` должен содержать: 69 | - информацию о проекте 70 | - краткую справку об инструментах и технологиях 71 | - инструкцию по настройке и запуску приложения 72 | * Используйте feature branches, pull requests. Отличная [статья](https://www.flagship.io/git-branching-strategies/) в которой сравниваютcя Git Branching Strategies. 73 | * Читаемая история коммитов. Можно использовать практику [Conventional commits](https://www.conventionalcommits.org/ru/v1.0.0/) 74 | * Должен быть настроен Continuous Integration (Gitlab CI / Github Actions) 75 | * Для `feature/` и `master` branches должен быть настроен: 76 | * запуск тестов + подсчёт [coverage](https://www.guru99.com/test-coverage-in-software-testing.html) 77 | * запуск линтера 78 | * Будет огромным плюсом если настроен Continuous Delivery - деплой приложения в одно или несколько окружений. (test/stage/prod) 79 | * Необязательно: настроенный [dependabot](https://docs.github.com/ru/code-security/dependabot/working-with-dependabot) 80 | 81 | # Code Style 82 | Перед разработкой приложения: 83 | * Настроен редактор или IDE: 84 | * VS Code 85 | * Visual Studio 86 | * PyCharm 87 | * IDEA 88 | * Vim, emacs 89 | * Установлен [EditorConfig](https://editorconfig.org/) плагин для твоего редактора 90 | * Установлены наиболее популярные инструменты по верификации качества кода, например 91 | * Rubocop for Ruby 92 | * Pylint/Black/PEP8 for Python 93 | 94 | 95 | # ✔️Tests 96 | * Установлены библиотеки для написания тестов различных видов (unit, integration). Например: 97 | * Pytest for Python 98 | * RSpec for Ruby 99 | * Testify, testcontainers for Golang 100 | * После прогона тестов автоматически считается test coverage 101 | * Пишите unit-тесты по паттерну [AAA (Arange Act Assert)](https://medium.com/@pjbgf/title-testing-code-ocd-and-the-aaa-pattern-df453975ab80) 102 | 103 | Старайтесь покрывать ваш код по [пирамиде тестирования](https://martinfowler.com/articles/practical-test-pyramid.html). Обратите внимание, что для тестов разного уровня могут использоваться разные инструменты. Для end to end тестирования можно использовать [Selenium](https://www.selenium.dev/) или [Cypress](https://www.cypress.io/how-it-works/). Для интеграционных удобно использовать `testcontainers` 104 | 105 | # ⚙️Configuration & Infrastructure around Code 106 | * На локальной машине разработчика установлены `Docker` и `docker-compose` 107 | * В репозитории есть Dockefile с помощью которого можно собрать приложение в `Docker container` 108 | * [Best practices when writing a Dockerfile for a Ruby application](https://lipanski.com/posts/dockerfile-ruby-best-practices) (хотя советы применимы и к другим языкам) 109 | * [Google Cloud: Best practices for building containers](https://cloud.google.com/architecture/best-practices-for-building-containers) 110 | * Все зависимости приложения (`PostgreSQL`, `S3`, `Redis`, `Kafka`, `RabbitMQ`) описаны в `docker-compose.yml` 111 | * Настройка приложения и запуск должны делаться максимально просто и прозрачно (для этого может понадобиться написать вспомогательные скрипты на `bash/zsh/powershell`) 112 | * [Приложение должно иметь несколько окружений (development, prod, test)](https://12factor.net/dev-prod-parity) 113 | * Для `production` сборки приложения используется рекомендуемый application сервер, например: 114 | * Puma for Ruby 115 | * Gunicorn3 for Python 116 | * Undertow for Java 117 | 118 | При описании конфигурации приложения используйте принципы 12factor. Изображение взято из статьи: [12 Factor App Revisited](https://architecturenotes.co/12-factor-app-revisited/) 119 | 120 | 121 | # Deployment 122 | В данном разделе будет приведены варианты того как можно задеплоить своё приложение, от простого и топорного до production ready практик. 123 | 124 | 125 | ## Level 0: Use free hostings 126 | Многие сервисы предоставляют бесплатный хостинг для небольших проектов, например: 127 | - https://render.com 128 | - https://www.netlify.com 129 | 130 | 131 | Больше вариантов здесь: https://free-for.dev/#/?id=web-hosting 132 | 133 | Изучите их перед тем как переходить к следующим уровням, возможно вам будет достаточно тех фич что они предлагают :) 134 | 135 | 136 | ## Level 1: SSH, Git, VPS / Cloud (EC2) (without Docker) 137 | Что требуется: 138 | - Купить сервер у облачного провайдера, убедиться что провайдер выдал вам IP адрес. 139 | - Разобраться с SSH и научиться подключаться к серверу с локального компьютера. 140 | - [Установить и настроить утилиты которые обеспечат базовую безопасность сервера](https://opensource.com/article/19/10/linux-server-security) 141 | - Установить и настроить на сервере инструменты которые необходимы для развертывания приложения 142 | - Git (для получения новых версий приложения) 143 | - Runtime приложения (нужен для интерпретируемых языков типа Ruby, Python, NodeJS) 144 | - Reverse Proxy (Nginx). Он будет являться точкой входа и заниматься раздачей статических файлов + перенаправлять запросы к приложению 145 | - Настроить автоматическую доставку новых версий кодовой базы на сервер (по кнопке с локального компьютера или например из CI). В этом могут помочь инструменты типа Capistrano, Fabric и многие другие. 146 | 147 | Что делать с базой данных? Есть несколько вариантов. 148 | - Настроить на том же сервере (подойдет для тестовых заданий, небольших пет проектов) 149 | - Настроить на отдельном сервере (лучше с точки зрения разделения ответственности но потребует от программиста следить не за одним а 2мя серверами + нужно всё равно понадобится умение конфигурировать, знать лучшие практики) 150 | - Купить managed СУБД у любого провайдера (AWS, Google, Yandex, SberCloud etc). Стоит денег но взамен следить за СУБД будет компания (бэкапы, обслуживание, обновление версий итп) 151 | 152 | Дополнительные пункты: 153 | - Купить доменное имя и настроить чтобы запросы к нему шли к серверу 154 | - Настроить Lets Encrypt сертификаты для данного домена + прописать сертификаты в Nginx (чтобы браузер не пугал пользователей сайта о том что сайт небезопасный :)) 155 | 156 | Задача со звездочкой: научиться писать Ansible playbooks, чтобы настройка сервера выполнялась в одну команду для программиста. 157 | 158 | 159 | ## Level 2: Containers and VPS (Docker required) 160 | Данный способ также подойдет для небольших проектов и для тех кто уверенно чувствует себя с Docker. 161 | 162 | Что нужно: 163 | - Настроить VPS аналогично Level 1 (SSH, Безопасность, пользователи итп) 164 | - Установить Docker. 165 | - Установить docker-compose на локальной машине разработчика. 166 | 167 | Деплой будет происходить через запуск контейнера на удаленной машине: 168 | ``` 169 | DOCKER_HOST=“ssh://user@your_vps_domain_or_ip” docker-compose up -d 170 | ``` 171 | 172 | Таким образом мы мы можем описать приложение и все его зависимости в docker-compose.yml и развернуть в одну команду. 173 | 174 | [Подробнее](https://docs.docker.com/compose/production/) 175 | 176 | 177 | ## Level 3: Containers and clouds (Docker required) 178 | Если от вас требуется задеплоить приложение в облако и вы уже знакомы с Docker, то всё будет гораздо проще и стандартизовано 179 | - [Deploying Docker containers on ECS](https://docs.docker.com/cloud/ecs-integration/) 180 | 181 | 182 | ## Level 4: Containers and Orchestrators (Docker required) 183 | - [Deploy to Swarm](https://docs.docker.com/get-started/swarm-deploy/) 184 | - [Deploy to Kubernetes](https://docs.docker.com/get-started/kube-deploy/) 185 | 186 | 187 | ## Bonus #1: Learn deployment strategies 188 | Существует несколько способов доставки новых версий приложения. Каждая из них имеет свои плюсы и минусы. Изучите их и выберите наиболее подходящую. Отличный практический репозиторий с объяснениями на примере k8s 189 | - [Kubernetes deployment strategies explained](https://github.com/ContainerSolutions/k8s-deployment-strategies) 190 | 191 | ## Bonus #2: Shutdown app gracefully with zero downtime 192 | Деплой приложения подразумевает что текущие инстансы приложения будут постепенно удаляться и их место займут новые. Чтобы клиент в момент релиза не получил негативный опыт взаимодействия важно перед тем как удалять инстансы терпеливо дожидаться завершения всех клиентских запросов (если речь о HTTP) и не допускать ситуации что в момент деплоя инстанс который завершает свою работу продолжает принимать HTTP запросы. 193 | 194 | [Как безопасно завершить работу пода в Kubernetes: разбираемся с graceful shutdown и zero downtime деплоймент](https://habr.com/en/companies/vk/articles/654471/) 195 | 196 | # API Design 197 | * Используй конвенции [REST](https://www.freecodecamp.org/news/rest-api-best-practices-rest-endpoint-design-examples/) как фундамент при именовании путей, типов операций и выборе статусов ответов API 198 | * Формат данных: JSON (если не требуется другого) 199 | * В репозитории есть возможность открыть [Swagger](https://swagger.io/) спецификацию для знакомства с API 200 | * Её можно написать самостоятельно 201 | * А можно генерировать c помощью утилит: [rswag (Rails)](https://github.com/rswag/rswag), [safrs (Flask)](https://github.com/thomaxxl/safrs), [echo-swagger (Echo/Golang)](https://github.com/swaggo/echo-swagger) 202 | 203 | Полезные ссылки: 204 | - [Zalando RESTful API and Event Guidelines](https://opensource.zalando.com/restful-api-guidelines/) 205 | 206 | 207 | Если считаешь что связка REST+JSON не подходит под задачу, или по заданию требуется другой формат, то стоит изучить альтернативы: 208 | - [SOAP](https://www.w3.org/TR/soap12-part1/) 209 | - [JSON-RPC](https://www.jsonrpc.org/) 210 | - [gRPC](https://grpc.io/) 211 | - [GraphQL](https://graphql.org/) 212 | 213 | # Authorization & Authentication 214 | **Аутентификация** – процедура проверки подлинности, например, проверка подлинности пользователя путем сравнения введенного им пароля с паролем, сохраненным в базе данных. 215 | 216 | В качестве аутентификации по API можно использовать: 217 | * HTTP Basic Auth (простой путь) 218 | * JSON Web Tokens (посложнее) 219 | 220 | Полезные ссылки 221 | - [Web Authentication Methods Compared](https://testdriven.io/blog/web-authentication-methods/) 222 | 223 | **Авторизация** – предоставление определенному лицу прав на выполнение определенных действий. 224 | Например: пользователь которого забанил администратор не может публиковать комментарии к постам (хотя он прошел аутентификацию на сайте). 225 | 226 | Примеры библиотек: 227 | - [Pundit for Ruby](https://github.com/varvet/pundit) 228 | - [Casbin (Many languages supported)](https://github.com/casbin/casbin) 229 | 230 | Дополнительные ссылки: 231 | - [Difference between RBAC vs. ABAC vs. ACL vs. PBAC vs. DAC](https://www.strongdm.com/blog/rbac-vs-abac) 232 | 233 | 234 | # MVC Explanation 235 | Цель: разделить обязанности в коде между компонентами. MVC это один из вариантов достижения цели и не требует от разработчика сильной когнитивной нагрузки (по сравнению с другими подходами) 236 | 237 | ## Controller 238 | - Принимает тело запроса, валидирует его на соответствие API 239 | - Проверяет authorization + authentication 240 | - Вызывает Service, передает ему данные 241 | - На основе возвращаемого значения от Service вызывает код формирующий нужный ответ API (через View) 242 | 243 | 244 | ## Model 245 | * Хранит только описание схемы данных и связи с другими моделями 246 | * Бизнес логики хранит по минимуму а лучше не хранит вообще 247 | * Используется для того чтобы делать запросы к БД на чтение и запись 248 | 249 | 250 | ## Service 251 | * Принимает данные от контроллера, валидирует тело 252 | * Использует Model для чтения или записи данных в БД. 253 | * Отвечает за бизнес-логику приложения 254 | 255 | 256 | ## View 257 | * Отвечает за то чтобы на основе данных сформировать API ответ. 258 | 259 | # 📐✏️👷‍♀️Architecture, Design Patterns, Refactoring, etc 260 | После того как MVC стал открытой книгой стоит углубиться и изучить: 261 | - подходы к построению архитектуры приложения 262 | - принципы которые помогают писать код устойчивый к изменениям. 263 | 264 | Бесплатные ресурсы, которые рекомендую для старта: 265 | - [Categorized overview of programming principles & design patterns](https://github.com/webpro/programming-principles) 266 | - [Refactoring Patterns and Design Patters Reference](https://refactoring.guru) 267 | - [Summary of "Clean code" by Robert C. Martin](https://gist.github.com/wojteklu/73c6914cc446146b8b533c0988cf8d29) 268 | - [Summary of "Clean Architecture" by Robert C. Martin](https://gist.github.com/ygrenzinger/14812a56b9221c9feca0b3621518635b) 269 | 270 | 271 | # 🔒CRUD: Validations 272 | Перед тем как сохранять данные в БД обязательно: 273 | - отвалидируйте данные на тип (там где ожидается строка пришла строка, где int там int итп) 274 | - и соответствие тела запроса API (если пользователь отправил поля которые не имеет права отправлять в БД мы должны их игнорировать) 275 | 276 | 277 | # CRUD: Database 278 | * Используйте ORM (или что-то подобное), если в задании не указано что нужно писать чистый SQL. 279 | * Проще для старта 280 | * Безопасно (Большинство ORM предоставляют защиту от SQL injections из коробки) 281 | * Используйте механизм миграций чтобы создавать таблицы и другие сущности в вашей БД (Rails Migrations, Flask-Migrate, etc) 282 | * При описании таблиц важно сразу указать всем столбцам необходимые constraints (NULLABLE, DEFAULT VALUE, UNIQUE, PRIMARY KEY) 283 | * При описании таблиц важно сразу указать индексы для столбцов по которым ожидается поиск. 284 | * Для защиты API от перебора можно использовать как PRIMARY KEY `uuid` вместо `serial` 285 | 286 | 287 | P.S. При описании миграций полезно подсматривать [сюда](https://github.com/ankane/strong_migrations), чтобы не написать миграцию которая может заблокировать БД. 288 | # CRUD: Operations 289 | ## LIST (HTTP GET) 290 | * Для каждого ресурса в ответе должно присутствовать ID. 291 | * Ресурсы должны быть отсортированными по какому либо признаку, например по времени создания. 292 | * API должен поддерживать пагинацию (чтобы не возвращать все сущности из БД за раз) [Разбор вариантов пагинации](https://dev.to/appwrite/this-is-why-you-should-use-cursor-pagination-4nh5) + [Примеры на PostgreSQL](https://www.citusdata.com/blog/2016/03/30/five-ways-to-paginate/) 293 | * Количество запросов к БД в рамках запроса должно быть фиксированным (Отсутствует [N+1 проблема](https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping)) 294 | 295 | API не должно возвращать все поля модели. 296 | Пример: если наше API возвращает список постов то оно должно возвращать: 297 | * ID 298 | * Название поста 299 | * Имя автора 300 | * Первые несколько предложений статьи (превью) 301 | 302 | Полный текст поста для этого эндпоинта не нужен. 303 | 304 | ## READ (HTTP GET) 305 | * Возвращаем полностью ресурс со всеми полями, ничего особенного 306 | 307 | 308 | ## CREATE (HTTP POST) 309 | * Валидируем данные на предмет полей которые пользователь не имеет права изменять в БД а следовательно передавать. 310 | * Делаем в БД INSERT 311 | * Возвращаем в ответ ID и содержимое. 312 | 313 | 314 | Задачи со звездочкой: 315 | - Убедиться что реализованное API идемпотентно: [Подробнее](https://habr.com/ru/company/yandex/blog/442762/) 316 | - Настроить Rate Limiter чтобы защитить БД от спама и мусора 317 | ## UPDATE (HTTP PUT/PATCH) 318 | * Разобраться в чем отличие между PUT и PATCH в HTTP 319 | * Валидировать тело запроса на предмет полей которые пользователь не имеет права изменять в БД а следовательно передавать. 320 | * Проверка права на редактирование у пользователя 321 | * Например API не должно позволять пользователю редактировать чужие комментарии :) 322 | * Реализовать обновление согласно выбранному методу 323 | 324 | 325 | ## DESTROY (HTTP DELETE) 326 | * Реализовать удаление предварительно проверив наличие сущности в БД и права на удаление у пользователя 327 | 328 | Дополнительно может быть полезно: реализовать soft удаление (скрываем от пользователя, оставляем в БД) 329 | 330 | 331 | # External API Calls, Long-running tasks (And why we need message queue) 332 | Если в рамках API требуется: 333 | - выполнять запросы к внешним системам 334 | - генерировать отчеты/выполнять долгие запросы к БД 335 | то стоит подумать о том чтобы делать эти операции за пределами HTTP запроса. 336 | 337 | Для этого может понадобиться очередь (Queue) в которую можно будет добавлять задачу (Task). 338 | 339 | Примеры высокоуровневых библиотек которых решают задачу с постановкой, чтением и обработкой задач: 340 | * Celery for Python (Задачи хранятся в `Redis`) 341 | * Sidekiq for Ruby (Задачи хранятся в `Redis`) 342 | 343 | Стоит отметить, что `Redis` это не единственный вариант для хранения очереди + не для всех задач он подходит. 344 | Поэтому полезно изучить как минимум 2 более продвинутых варианта для хранения и обработки очередей: `RabbitMQ` и `Kafka`. 345 | 346 | Доп.ссылки: 347 | - [RabbitMQ и Apache Kafka: что выбрать](https://slurm.io/tpost/phdmogo9y1-rabbitmq-i-apache-kafka-chto-vibrat-i-mo) 348 | - [Latency, throughput, and availability: system design interview concepts](https://igotanoffer.com/blogs/tech/latency-throughput-availability-system-design-interview) - Подробнее о том, почему так важно чтобы HTTP запросы были быстрыми 349 | 350 | # Metrics 351 | - Настроить Prometheus метрики с информацией о состоянии HTTP API и райнтайме приложения. Рекомендуется использовать готовые пакеты, которые собирают метрики о работе приложения по методикам [RED (Rate Error Duration)](https://www.infoworld.com/article/3638693/the-red-method-a-new-strategy-for-monitoring-microservices.html) и [USE (Utilization Saturation Errors)](https://www.brendangregg.com/usemethod.html): 352 | - [prometheus, promauto, promhttp для Go](https://prometheus.io/docs/guides/go-application/) 353 | - [starlette-prometheus для Python](https://github.com/perdy/starlette-prometheus) 354 | 355 | 356 | # Logs 357 | - [Логи должны писаться только в stdout](https://12factor.net/logs) 358 | - [Логи должны иметь строгий формат, например это может быть JSON](https://coralogix.com/blog/json-logging-why-how-what-tips/) 359 | - [Используйте structured logging подход для явного отделения логируемого сообщения от контекста в котором оно произошло](https://stackify.com/what-is-structured-logging-and-why-developers-need-it/) 360 | 361 | # 🛡️Security 362 | - Убедись, что не используешь версии библиотек в которых есть уязвимости, проверять это можно автоматически с помощью утилит, например: 363 | - [bundler-audit for Ruby](https://github.com/rubysec/bundler-audit) 364 | - [pip-audit for Python](https://pypi.org/project/pip-audit/) 365 | - [local-php-security-checker for PHP](https://github.com/fabpot/local-php-security-checker) или команда `symfony check:security`, если используется фреймворк [Symfony](https://symfony.com/) 366 | - Настрой dependabot, который будет автоматически обновлять версии библиотек 367 | - Убедись, что приложение достаточно защищено от актуальных уязвимостей - [OWASP TOP 10](https://owasp.org/www-project-top-ten/). Помочь в этом нелегком деле может [чеклист №1](https://github.com/shieldfy/API-Security-Checklist) и [№2 (с примерами на Ruby on Rails)](https://github.com/brunofacca/zen-rails-security-checklist) 368 | 369 | 370 | # CORS Headers (Cross-Origin Resource Sharing) 371 | Если запросы к твоему API будут делать из браузерных скриптов, например Single Page Application, построенных на современных Javascript фреймворках (React, Angular, Vue.js) и домен API будет отличаться от домена клиентского приложения, то нужно в API добавить CORS заголовки, чтобы браузер не блокировал ответы от API. 372 | 373 | 374 | Обычно, модуль для настройки CORS заголовков есть в http фреймворке и можно использовать его как по дефолту, так и с более тонкими настройками по необходимости. Например: 375 | - [Go Echo](https://echo.labstack.com/middleware/cors/) 376 | - [Javascript ExpressJS](https://expressjs.com/en/resources/middleware/cors.html) 377 | 378 | Подробнее про CORS заголовки можно прочитать [здесь](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). 379 | 380 | # 🚄Cache 381 | Первым делом нужно задать себе вопросы: 382 | - "зачем нужен кеш, какую проблему он решит?" 383 | - "можно ли обойтись без кеша?" (например изменив схему СУБД, потюнить настройки, добавить индексы итп) 384 | 385 | Если твердо решили что нужен кеш то: 386 | - Выбрать [стратегию кеширования / инвалидации кеша](https://codeahoy.com/2017/08/11/caching-strategies-and-how-to-choose-the-right-one/) исходя из: 387 | - логики приложения 388 | - профиля нагрузки (read heavy, write heavy) 389 | - ресурсов разработки (некоторые стратегии достаточно сложны в реализации) 390 | - Выбрать размер кеша + стратегию вытеснения (так как невозможно хранить в кеше абсолютно всё). Наиболее популярной стратегией является Least Recently Used (LRU). 391 | - Выбрать место хранения: 392 | - RAM (в инстансах приложения) 393 | - СУБД (Redis, Memcached, Dynamo, etcd) 394 | - Описать метрику для отслеживания эффективности кеша (cache hit rate) и при необходимости изменять размер кеша или изменить стратегию кеширования. 395 | 396 | # Remote Config / Feature Toggles / Feature Flags 397 | Для ускорения процесса доставки функциональности полезно использовать динамическую конфигурацию вместе со статической (переменные окружения). Изменения динамической конфигурации позволяет изменить поведение программы без дополнительных деплоев и перезапусков. 398 | 399 | Полезные ссылки: 400 | - [Фича Флаги и управление ими “по-взрослому”: кейс команды СберЗдоровье](https://habr.com/ru/companies/docdoc/articles/742962/) 401 | - [Open-source feature management solution built for developers.](https://github.com/Unleash/unleash) 402 | - [Flagr is a feature flagging, A/B testing and dynamic configuration microservice](https://github.com/openflagr/flagr) 403 | 404 | # Full Text Search 405 | # Transactions, Locks, Isolation Levels, ACID 406 | Классические веб приложения подразумевают что в единицу времени может быть несколько пользователей (сотни / тысячи / миллионы). И дополнительно к этому пользователи могут взаимодействовать с одним общим ресурсом одновременно (классический сценарий - покупка билетов на мероприятие). Для того чтобы избежать состояний гонки, ошибок и нарушений бизнес-логики важно правильно организовать работу с базой данных как минимум: 407 | - использовать транзакции чтобы не допустить промежуточных состояний. 408 | - избегать конкурентности в критичных местах за счет механизма "lock-ов" 409 | 410 | [Отличная статья которая даст начальное представление о проблемах конкурентности и согласованности в API](https://www.strv.com/blog/database-transactions-lost-updates-idempotency-engineering) 411 | --------------------------------------------------------------------------------