Налаштування DynamoDB для веб-застосунку

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Налаштування DynamoDB для веб-застосунку
Складна
~2-3 робочих дні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Налаштування DynamoDB для веб-додатку

DynamoDB — керована NoSQL база від AWS з гарантованою затримкою в мілісекунди при будь-якому масштабі. Немає серверів для обслуговування, нема ручного шардування, немає проблем із лагом реплік. Ідеально для serverless-архітектур і додатків із непередбачуваними піками навантаження.

Ключові концепції перед проектуванням

DynamoDB — не реляційна база і не MongoDB. Немає JOIN, немає агрегатних запитів без повного скану, немає гнучких фільтрів. Усі запити будуються навколо Partition Key (PK) та Sort Key (SK). Single-table design — стандартний підхід: одна таблиця для всіх сутностей додатку з перевантаженими PK/SK.

Створення таблиці через AWS CDK (TypeScript)

import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'
import { RemovalPolicy } from 'aws-cdk-lib'

const table = new dynamodb.Table(this, 'AppTable', {
  tableName: 'MyApp',
  partitionKey: { name: 'PK', type: dynamodb.AttributeType.STRING },
  sortKey: { name: 'SK', type: dynamodb.AttributeType.STRING },
  billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,  // або PROVISIONED + auto scaling
  pointInTimeRecovery: true,
  deletionProtection: true,
  removalPolicy: RemovalPolicy.RETAIN,

  // Streams для реакції на зміни
  stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES,
})

// GSI для пошуку за email
table.addGlobalSecondaryIndex({
  indexName: 'GSI1',
  partitionKey: { name: 'GSI1PK', type: dynamodb.AttributeType.STRING },
  sortKey: { name: 'GSI1SK', type: dynamodb.AttributeType.STRING },
  projectionType: dynamodb.ProjectionType.ALL,
})

Single-table design: приклад схеми

Сутність: User
PK: USER#<userId>    SK: METADATA
GSI1PK: EMAIL#<email>   GSI1SK: USER#<userId>

Сутність: Order
PK: USER#<userId>    SK: ORDER#<orderId>
GSI1PK: ORDER#<orderId> GSI1SK: USER#<userId>

Сутність: OrderItem
PK: ORDER#<orderId>  SK: ITEM#<itemId>

Сутність: Product
PK: PRODUCT#<productId>  SK: METADATA
GSI1PK: CATEGORY#<cat>   GSI1SK: PRODUCT#<productId>

DynamoDB клієнт (AWS SDK v3)

import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import {
  DynamoDBDocumentClient,
  GetCommand,
  PutCommand,
  QueryCommand,
  UpdateCommand,
  DeleteCommand,
  TransactWriteCommand
} from '@aws-sdk/lib-dynamodb'

const client = new DynamoDBClient({ region: process.env.AWS_REGION })
const db = DynamoDBDocumentClient.from(client, {
  marshallOptions: { removeUndefinedValues: true }
})

const TABLE = 'MyApp'

Репозиторій користувачів

export class UserRepository {
  async create(user: CreateUserInput): Promise<User> {
    const id = crypto.randomUUID()
    const now = new Date().toISOString()

    await db.send(new TransactWriteCommand({
      TransactItems: [
        {
          Put: {
            TableName: TABLE,
            Item: {
              PK: `USER#${id}`,
              SK: 'METADATA',
              GSI1PK: `EMAIL#${user.email}`,
              GSI1SK: `USER#${id}`,
              id,
              email: user.email,
              name: user.name,
              passwordHash: user.passwordHash,
              role: 'user',
              createdAt: now,
              updatedAt: now,
              _type: 'User'
            },
            ConditionExpression: 'attribute_not_exists(PK)'
          }
        }
      ]
    }))

    return { id, ...user, role: 'user', createdAt: now, updatedAt: now }
  }

  async findByEmail(email: string): Promise<User | null> {
    const result = await db.send(new QueryCommand({
      TableName: TABLE,
      IndexName: 'GSI1',
      KeyConditionExpression: 'GSI1PK = :pk',
      ExpressionAttributeValues: { ':pk': `EMAIL#${email}` },
      Limit: 1
    }))

    return (result.Items?.[0] as User) ?? null
  }

  async findById(id: string): Promise<User | null> {
    const result = await db.send(new GetCommand({
      TableName: TABLE,
      Key: { PK: `USER#${id}`, SK: 'METADATA' }
    }))

    return (result.Item as User) ?? null
  }
}

Замовлення з розбиванням на сторінки

export class OrderRepository {
  async listForUser(userId: string, limit = 25, lastKey?: Record<string, unknown>) {
    const result = await db.send(new QueryCommand({
      TableName: TABLE,
      KeyConditionExpression: 'PK = :pk AND begins_with(SK, :prefix)',
      ExpressionAttributeValues: {
        ':pk': `USER#${userId}`,
        ':prefix': 'ORDER#'
      },
      ScanIndexForward: false,  // новіші спочатку
      Limit: limit,
      ExclusiveStartKey: lastKey
    }))

    return {
      items: result.Items as Order[],
      nextKey: result.LastEvaluatedKey
    }
  }

  async createWithItems(order: CreateOrderInput): Promise<Order> {
    const orderId = crypto.randomUUID()
    const now = new Date().toISOString()

    const items: TransactWriteItem[] = [
      {
        Put: {
          TableName: TABLE,
          Item: {
            PK: `USER#${order.userId}`,
            SK: `ORDER#${now}#${orderId}`,  // now в SK для сортування за датою
            GSI1PK: `ORDER#${orderId}`,
            GSI1SK: `USER#${order.userId}`,
            id: orderId,
            userId: order.userId,
            status: 'pending',
            total: order.total,
            createdAt: now,
            _type: 'Order'
          }
        }
      },
      ...order.items.map(item => ({
        Put: {
          TableName: TABLE,
          Item: {
            PK: `ORDER#${orderId}`,
            SK: `ITEM#${item.productId}`,
            orderId,
            productId: item.productId,
            quantity: item.quantity,
            price: item.price,
            _type: 'OrderItem'
          }
        }
      }))
    ]

    await db.send(new TransactWriteCommand({ TransactItems: items }))
    return { id: orderId, ...order, status: 'pending', createdAt: now }
  }
}

Налаштування DynamoDB Streams + Lambda

// Обробка змін у реальному часі
export const handler = async (event: DynamoDBStreamEvent) => {
  for (const record of event.Records) {
    if (record.eventName !== 'MODIFY') continue

    const newImage = unmarshall(record.dynamodb!.NewImage!)
    const oldImage = unmarshall(record.dynamodb!.OldImage!)

    if (newImage._type === 'Order' && newImage.status !== oldImage.status) {
      await notifyOrderStatusChange(newImage.id, newImage.status)
    }
  }
}

Моніторинг через CloudWatch

Ключові метрики: ConsumedReadCapacityUnits, ConsumedWriteCapacityUnits, SuccessfulRequestLatency, SystemErrors, ThrottledRequests. На ThrottledRequests > 0 — негайний алерт.

Терміни

Проектування single-table схеми + базова інтеграція з Node.js/Python: 3–5 днів. Додавання GSI, Streams, Lambda-обробників та моніторингу: ще 3–5 днів. Міграція з реляційної бази в DynamoDB з переглядом шаблонів доступу: 2–4 тижні — це не механічна конвертація, а переосмислення моделі даних.