Как мы создали платформу с несколькими приложениями на едином коде
Alphazed управляет более 7 образовательными приложениями (Amal, Thurayya, Qais, KidElite, Alphazed School, Alphazed Montessori и другие) на едином исходном коде backend и общем Flutter-фреймворке для мобильных приложений. Каждое приложение получает собственные таблицы базы данных (с префиксом), настройки, push-уведомления, шаблоны писем и контент — при этом используется общая аутентификация (AWS Cognito), инфраструктура аналитики и основные алгоритмы обучения.
Backend: выбор приложения во время выполнения
Как это работает
При деплое переменная окружения выбирает приложение:
# Деплой Amal
export APP_NAME=amal
serverless deploy
# Деплой Thurayya
export APP_NAME=thurayya
serverless deploy
# Деплой Qais
export APP_NAME=qais
serverless deploy
Каждый деплой создает независимые функции Lambda, маршруты API Gateway и мониторинг — при этом все они подключаются к одному и тому же backend-коду.
Приоритет конфигураций с тремя уровнями
# src/config.py
import os
app_name = os.getenv('APP_NAME', 'amal')
# Уровень 1: точное совпадение приложения
config = load_json(f'config/{app_name}.json')
# Уровень 2: семейство приложений (если точной конфигурации нет)
if not config:
family = app_name.split('_')[0] # 'amal_beta' → 'amal'
config = load_json(f'config/{family}.json')
# Уровень 3: по умолчанию
if not config:
config = load_json('config/default.json')
Конфигурация для отдельного приложения (config/amal.json)
{
"app_name": "amal",
"app_id": "com.alphazed.amal",
"database": {
"table_prefix": "amal_"
},
"email": {
"template_dir": "templates/amal",
"from_address": "amal@alphazed.com",
"from_name": "Amal Team"
},
"push_notifications": {
"firebase_project": "alphazed-amal",
"apns_certificate": "certs/amal.pem"
},
"content": {
"s3_bucket": "amal-content-production",
"curriculum_id": "amal_v3_arabic"
},
"feature_flags": {
"enable_tajweed_feedback": false,
"enable_noorani_qaida": false,
"enable_juz_amma": false,
"enable_creature_building": true,
"enable_physics_games": true
},
"pricing": {
"monthly_usd": 6.99,
"yearly_usd": 67.99,
"trial_days": 14
}
}
Общие и отдельные ресурсы
| Ресурс | Общий | Отдельный | Причина |
|---|---|---|---|
| Код Lambda | ✓ Да | ✗ Нет | Единый код с ветвлением по APP_NAME |
| RDS-инстанс | ✓ Да | ✗ Нет (префиксы в таблицах) | Снижение стоимости, единый бэкап |
| Контент в S3 | ✗ Нет | ✓ Да | У каждого приложения своя учебная программа |
| AWS Cognito | ✓ Да | ✗ Нет (через app_id) | Централизованная аутентификация, разграничение по app_id |
| Аналитика | ✓ Да | ✗ Нет (разделена по приложениям) | Единый pipeline, отдельные данные |
| Firebase Messaging | ✗ Нет | ✓ Да | Отдельные APNs и FCM для каждого приложения |
Frontend: Flutter-мультиприложения
Общее ядро (packages/alphazed_common)
- Управление состоянием (Riverpod)
- Обработка аудио
- Библиотека анимаций (интеграция Rive)
- Дизайн-система (цвета, типографика, виджеты)
- Авторизационные потоки
- Клиент аналитики
Слои, специфичные для каждого приложения (apps/amal, apps/thurayya и др.)
apps/
├── amal/
│ ├── lib/
│ │ ├── main.dart (точка входа в приложение)
│ │ ├── config/
│ │ │ ├── curriculum.json (структура уроков Amal)
│ │ │ ├── colors.dart (тема Amal)
│ │ │ └── characters.json (настройка аватаров)
│ │ └── screens/ (экраны для Amal)
│ └── pubspec.yaml (зависимости Amal)
│
├── thurayya/
│ ├── lib/
│ │ ├── main.dart (отдельная точка входа)
│ │ ├── config/
│ │ │ ├── curriculum.json (структура Juz Amma)
│ │ │ ├── colors.dart (тема Thurayya)
│ │ │ └── characters.json (настройка аватаров)
│ │ └── screens/ (экраны для Thurayya)
│ └── pubspec.yaml (зависимости Thurayya)
│
└── packages/
└── alphazed_common/ (общий код)
Конфигурация на этапе сборки
# Сборка Amal
flutter build apk --dart-define=APP_NAME=amal --dart-define=CURRICULUM=amal_v3
# Сборка Thurayya
flutter build apk --dart-define=APP_NAME=thurayya --dart-define=CURRICULUM=thurayya_juzamma
Во время сборки каждое приложение получает свои данные учебной программы, цвета и конфигурацию. Один репозиторий, несколько скомпилированных бинарников.
Деплой: независимые стэки
Конфигурация Serverless Framework
# serverless.yml
service: alphazed-backend
custom:
pythonRequirements:
dockerizePip: true
app_name: ${env:APP_NAME}
functions:
# Эти функции разворачиваются для каждого APP_NAME
get_user:
handler: src/handlers/user.get_user
events:
- http:
path: app/{app_name}/user/{user_id}
method: get
content_duo:
handler: src/handlers/content.generate_content_duo
timeout: 20
events:
- http:
path: app/{app_name}/content_duo
method: post
resources:
Resources:
# Для каждого приложения создается своя группа логов CloudWatch
${self:custom.app_name}LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: /aws/lambda/alphazed-${self:custom.app_name}
RetentionInDays: 30
При деплое с APP_NAME=amal CloudFormation создаёт:
- Lambda-функции с префиксом
alphazed-amal-* - Маршруты API Gateway с путями
/amal/* - Логи CloudWatch в группе
/aws/lambda/alphazed-amal - Независимое масштабирование и мониторинг
Почему это важно
Для продуктовых команд
- Запуск нового приложения за недели, а не месяцы благодаря общей инфраструктуре
- Параллельность функций: исправления ошибок и улучшения алгоритмов сразу для всех приложений
- A/B тестирование фич на одном приложении перед массовым релизом
Для инженерных команд
- Единая кодовая база, один набор тестов, единый CI/CD
- Общие алгоритмы обучения (интервальное повторение и микс контента) выгодны всем приложениям
- Удобство управления инфраструктурой одной командой
Для бюджета
- Общий RDS — в разы дешевле, чем 7 отдельных баз
- Общий Cognito — одна система авторизации для всех
- Общая аналитика — одна сборка для 7 приложений
- Экономия около 40-60 тыс. долларов в год по сравнению с раздельными backend
Задачи и решения
Проблема 1: конфликты схем базы данных
- Amal использует таблицу
amal_user_memory - Thurayya использует
thurayya_user_memoryс другими полями для запоминания Корана - Решение: миграции зависят от
APP_NAME. Например,migrations/001_amal_user_memory.sqlвыполняется только дляAPP_NAME=amal
Проблема 2: разные наборы функций
- В Thurayya есть обратная связь по таджвиду, в Amal — нет
- В Amal есть игры по физике, в Thurayya — нет
- Решение: Feature flags в конфигурации (
enable_tajweed_feedback,enable_physics_games) управляют доступностью функций
Проблема 3: независимое масштабирование
- Если у Amal резкий рост трафика, а у Thurayya затишье
- Общий пул Lambda-функций конкурирует за ресурсы
- Решение: политики масштабирования CloudWatch для каждого приложения. При достижении лимита у
amal-*происходит авторасширение, не затрагивая Thurayya
FAQ
Вопрос: Не вызывает ли общий кодовую базу сильную связанность приложений?
Ответ: Нет. Код каждого приложения находится в отдельных папках (apps/amal, apps/thurayya). Общий код — в packages/alphazed_common, с явным контролем версий. Сильная связанность — признак плохого дизайна, мы проверяем это на ревью кода.
Вопрос: Что если одному приложению нужна несовместимая с другими версия API?
Ответ: У нас версии API для каждого приложения: /amal/v1/*, /thurayya/v1/*. Они могут обновляться независимо. Старые версии поддерживаются 12 месяцев для плавного перехода.
Вопрос: Можно ли использовать общих пользователей для разных приложений?
Ответ: По умолчанию нет. У каждого приложения своя таблица пользователей с префиксом. Если родитель хочет подписаться и на Amal, и на Thurayya, нужно создать отдельные аккаунты (планируется функция «семейной» связки пользователей).



