DynamoDB - это облачный сервис AWS, который предоставляет базу данных «ключ-значение» и документов со встроенным автоматическим масштабированием, отказоустойчивостью с помощью многорегиональных глобальных таблиц и отличной поддержкой безопасности, резервного копирования и восстановления. На данный момент это одно из лучших решений для хранения веб-приложений.

Сегодня я расскажу, как быстро создать CRUD API для вашего приложения с DynamoDB и Spring Boot.

  1. Используйте Spring Initializr (https://start.spring.io/) для создания проекта начальной загрузки Spring. Добавьте Spring Web Starter в качестве зависимостей с помощью опций ниже. Я также решил добавить ломбок, потому что это поможет нам уменьшить шаблонный код.

2. Откройте проект в вашей любимой IDE. Импорт спецификации, чтобы все ваши пакеты aws sdk использовали одну и ту же совместимую версию:

<dependencyManagement>
   <dependencies>
      <dependency>
         <groupId>com.amazonaws</groupId>
         <artifactId>aws-java-sdk-bom</artifactId>
         <version>1.11.614</version>
         <type>pom</type>
         <scope>import</scope>
      </dependency>
   </dependencies>
</dependencyManagement>

Добавьте зависимость aws sdk Dynamodb.

<dependency>
   <groupId>com.amazonaws</groupId>
   <artifactId>aws-java-sdk-dynamodb</artifactId>
</dependency>

Обратите внимание, что я использую версию 1.11.X aws java sdk. Сейчас доступен sdk версии 2.0. Он обеспечивает лучшие возможности асинхронного программирования, что теоретически поможет с производительностью, когда объем действительно велик. Однако в нем по-прежнему отсутствуют многие функции 1.11.X, что очень затрудняет разработку с использованием SDK 2.0. Поэтому пока я все еще рекомендую использовать 1.11.X, пока они 2.0 не достигнут паритета функций.

Вот проблема для добавления модуля отображения Dynamodb в версию 2.0, если вам интересно: https://github.com/aws/aws-sdk-java-v2/issues/35

3. Создайте клиент DynamoDB и средство сопоставления DynamoDB.

@Configuration
public class DynamoDBConfig {

    @Bean
    public DynamoDBMapper dynamoDBMapper() {
        AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
                .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials("XXX", "XXX")))
                .withRegion(Regions.US_WEST_2)
                .build();
        return new DynamoDBMapper(client, DynamoDBMapperConfig.DEFAULT);
    }
}

Для быстрого старта достаточно использовать идентификатор ключа доступа пользователя IAM и секретный ключ доступа. Но позже будет сложно решить, как зашифровать и где хранить эту информацию. Позже рекомендуется использовать аутентификацию на основе ролей IAM и получить временный токен для подключения к DynamoDB.

Ссылка: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html

4. Создайте класс сущности (объект POJO). Мы воспользуемся API более высокого уровня под названием DynamoDBMapper. Это позволяет нам аннотировать наш класс POJO в режиме гибернации и легко сопоставлять наш класс с объектом db. Список аннотаций, доступных через DynamoDBMapper, можно найти здесь: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBMapper.Annotations.html

Я также использую здесь ломбок, чтобы уменьшить шаблонный код. Он автоматически создаст для нас геттеры, сеттеры и конструкторы.

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DynamoDBTable(tableName = "dev-users")
public class User {

    @DynamoDBHashKey(attributeName = "userId")
    @DynamoDBAutoGeneratedKey
    private String userId;

    @DynamoDBAttribute(attributeName = "userName")
    private String userName;

    @DynamoDBAttribute(attributeName = "email")
    private String email;

    @DynamoDBAttribute(attributeName = "phoneNumber")
    private String phoneNumber;
}

5. Создайте Rest Controller для обработки запросов, я обрабатываю исключения, которые aws может генерировать, когда что-то пошло не так, и возвращаю клиенту соответствующий статус http. Для получения информации о том, какие исключения может генерировать Dynamodb: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.Errors.html

@RestController
public class UserCrudController {

    private UserCrudService userCrudService;

    @Autowired
    public UserCrudController(UserCrudService userCrudService) {
        this.userCrudService = userCrudService;
    }

    @RequestMapping(value = "/users", produces = {"application/json"}, method = RequestMethod.POST)
    public ResponseEntity createUser(@RequestBody User user) {
        try {
            User response = userCrudService.createUser(user);
            return ResponseEntity.status(HttpStatus.CREATED).body(response);
        } catch (AmazonServiceException e) {
            throw new ResponseStatusException(HttpStatus.valueOf(e.getStatusCode()), e.getMessage(), e);
        } catch (AmazonClientException e) {
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e);
        }
    }

    @RequestMapping(value = "/users/{userId}", produces = {"application/json"}, method = RequestMethod.GET)
    public ResponseEntity readUser(@PathVariable String userId) {
        try {
            User response = userCrudService.readUser(userId);
            return ResponseEntity.status(HttpStatus.OK).body(response);
        } catch (AmazonServiceException e) {
            throw new ResponseStatusException(HttpStatus.valueOf(e.getStatusCode()), e.getMessage(), e);
        } catch (AmazonClientException e) {
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e);
        }
    }

    @RequestMapping(value = "/users", produces = {"application/json"}, method = RequestMethod.PUT)
    public ResponseEntity updateUser(@RequestBody User user) {
        try {
            User response = userCrudService.updateUser(user);
            return ResponseEntity.status(HttpStatus.OK).body(response);
        } catch (AmazonServiceException e) {
            throw new ResponseStatusException(HttpStatus.valueOf(e.getStatusCode()), e.getMessage(), e);
        } catch (AmazonClientException e) {
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e);
        }
    }

    @RequestMapping(value = "/users/{userId}", produces = {"application/json"}, method = RequestMethod.DELETE)
    public ResponseEntity deleteUser(@PathVariable String userId) {
        try {
            userCrudService.deleteUser(userId);
            return ResponseEntity.status(HttpStatus.OK).body(null);
        } catch (AmazonServiceException e) {
            throw new ResponseStatusException(HttpStatus.valueOf(e.getStatusCode()), e.getMessage(), e);
        } catch (AmazonClientException e) {
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e);
        }
    }
}

6. Создайте уровень сервиса для выполнения некоторой бизнес-логики и вызовите dao для фактического чтения и записи данных.

@Service
public class UserCrudServiceImpl implements UserCrudService {

    private UserCrudDao userCrudDao;

    @Autowired
    public UserCrudServiceImpl(UserCrudDao userCrudDao) {
        this.userCrudDao = userCrudDao;
    }

    @Override
    public User createUser(User user) {
        return userCrudDao.createUser(user);
    }

    @Override
    public User readUser(String userId) {
        return userCrudDao.readUser(userId);
    }

    @Override
    public User updateUser(User user) {
        return userCrudDao.updateUser(user);
    }

    @Override
    public void deleteUser(String userId) {
        userCrudDao.deleteUser(userId);
    }
}

В настоящее время наш уровень обслуживания очень прост, в основном он просто пересылает информацию для чтения и записи dao. Позже мы можем добавить проверку, вызовы других сервисов, асинхронное программирование и т. Д.

7. Создайте слой дао. Обратите внимание, что я добавил условную проверку для обновления / удаления, потому что мы хотим обновить / удалить документ в DynamoDB только в том случае, если он был ранее помещен в базу данных.

@Component
public class UserCrudDaoImpl implements UserCrudDao {

    private DynamoDBMapper dynamoDBMapper;

    @Autowired
    public UserCrudDaoImpl(DynamoDBMapper dynamoDBMapper) {
        this.dynamoDBMapper = dynamoDBMapper;
    }

    @Override
    public User createUser(User user) {
        dynamoDBMapper.save(user);
        return user;
    }

    @Override
    public User readUser(String userId) {
        return dynamoDBMapper.load(User.class, userId);
    }

    @Override
    public User updateUser(User user) {
        Map<String, ExpectedAttributeValue> expectedAttributeValueMap = new HashMap<>();
        expectedAttributeValueMap.put("userId", new ExpectedAttributeValue(new AttributeValue().withS(user.getUserId())));
        DynamoDBSaveExpression saveExpression = new DynamoDBSaveExpression().withExpected(expectedAttributeValueMap);
        dynamoDBMapper.save(user, saveExpression);
        return user;
    }

    @Override
    public void deleteUser(String userId) {
        Map<String, ExpectedAttributeValue> expectedAttributeValueMap = new HashMap<>();
        expectedAttributeValueMap.put("userId", new ExpectedAttributeValue(new AttributeValue().withS(userId)));
        DynamoDBDeleteExpression deleteExpression = new DynamoDBDeleteExpression().withExpected(expectedAttributeValueMap);
        User user = User.builder()
                .userId(userId)
                .build();
        dynamoDBMapper.delete(user, deleteExpression);
    }
}

8. Создайте свою таблицу DynamoDB в консоли AWS. Нам нужно будет определить имя таблицы и первичный ключ (хеш-ключ). При желании мы также можем определить какой-нибудь ключ сортировки, если это необходимо. Щелкните «Создать».

9. Протестируйте свой CRUD API! Разместите объект пользователя на http: // localhost: 8080 / users

{
 “userName”: “abc”,
 “email”: “[email protected]”,
 “phoneNumber”: “1234567890”
}

Вы получите в ответ

{
  "userId": "08f546e2-5e7e-40b5-af39-2bc03ec2c569",
  "userName": "abc",
  "email": "[email protected]",
  "phoneNumber": "1234567890"
}

UserId автоматически генерируется DynamoDB, потому что мы добавили аннотацию @DynamoDBAutoGeneratedKey к первичному ключу, который мы определили в классе POJO.

Заключение:

С помощью этих простых шагов у вас теперь есть отличная отправная точка для хранения и извлечения данных пользователей, необходимых для вашего веб-приложения. Для получения дополнительной информации о том, как улучшить ваше приложение с помощью DynamoDB, вы можете посмотреть здесь: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.html