Паттерн проектування для роботи з Azure CosmosDB

Привіт. Зайшла задача на автоматизацію API тестів та БД CosmosDB. Не можу найти якогось паттерна проектування для робити з цією БД. Я раніше на робив з Azure DB, можливо щось не знаю де шукати.

Для роботи з CosmosDB використав бібліотеку 'com.microsoft.azure:azure-storage:8.6.0' і зєднуюсь з БД через CloudStorageAccount. Потрібно буде робити запити типу Select, Update, Insert, Delete.

Для прикладу, код запиту виглядає так:

String config = "DefaultEndpointsProtocol=https;AccountName=name;AccountKey=key;TableEndpoint=https://endpoint.table.cosmos.azure.com:443/";
        CloudStorageAccount cloudStorageAccount = CloudStorageAccount.parse(config);

        CloudTableClient tableClient = cloudStorageAccount.createCloudTableClient();
        try {
            CloudTable cloudTable = tableClient.getTableReference("tableName");
            cloudTable.createIfNotExists();

            TableQuery<TestEntity> query =
                    new TableQuery<TestEntity>()
                            .where(TableQuery.generateFilterCondition("ColumnName",
                                    TableQuery.QueryComparisons.EQUAL, "testValue"));
            Iterable<TestEntity> it = cloudTable.execute(query);
            List<TestEntity> list= StreamSupport.stream(it.spliterator(), false).collect(Collectors.toList());

        } catch (StorageException e) {
            e.printStackTrace();
        }

TestEntity - це клас, який реалізовує колонки табличики.

При реалізації своєїї абстракції зіштовхнувся з такими питаннями:

  1. Для роботи з БД використувують Sigleton для створення одного конекшина, не розумію, як його зробити з CloudStorageAccount.
  2. Осікльки ми робимо для кожної таблички окремий клієнт CloudTable cloudTable = tableClient.getTableReference("tableName");, відповідно для кожної таблички потрібно буде зробити свій клас з запитами. Але тоді сильно буду завязуватись на реалізації query
TableQuery<TableEntity> query =
                    new TableQuery<TableEntity>()
                            .where(TableQuery.generateFilterCondition(columnName,
                                    operation, value));

а саме на типі фільтрів where (можуть такі варіанти select, take, getColumns ітд) і на типі порівнювального оператора TableQuery.QueryComparisons.EQUAL (можуть бути NOT_EQUAL, GREATER_THAN ітд)

Приклад методу:

    public static List<TableEntity> select(String columnName, String operation, String value) {
        Iterable<TableEntity> resultQuery = null;
        try {
            CloudStorageAccount cloudStorageAccount = CloudStorageAccount.parse(config);
            CloudTableClient tableClient = cloudStorageAccount.createCloudTableClient();
            CloudTable cloudTable = tableClient.getTableReference(tableName);
            cloudTable.createIfNotExists();

            TableQuery<TableEntity> query =
                    new TableQuery<TableEntity>()
                            .where(TableQuery.generateFilterCondition(columnName,
                                    operation, value));

            resultQuery = cloudTable.execute(query);

        } catch (StorageException | URISyntaxException | InvalidKeyException e) {
            e.printStackTrace();
        }
        return StreamSupport.stream(resultQuery.spliterator(), false).collect(Collectors.toList());
    }

або метод, який вже приймає запит

    public static List<TableEntity> select(TableQuery<TableEntity> query) {
        Iterable<TableEntity> resultQuery = null;
        try {
            CloudStorageAccount cloudStorageAccount = CloudStorageAccount.parse(config);
            CloudTableClient tableClient = cloudStorageAccount.createCloudTableClient();
            CloudTable cloudTable = tableClient.getTableReference(tableName);
            cloudTable.createIfNotExists();

            resultQuery = cloudTable.execute(query);

        } catch (StorageException | URISyntaxException | InvalidKeyException e) {
            e.printStackTrace();
        }
        return StreamSupport.stream(resultQuery.spliterator(), false).collect(Collectors.toList());
    }

Прошу допомогти створити цей паттерн або якщо хтось може поділитись паттерном/дати силку на паттерн проектування для роботи з Microsoft Azure CosmosDB, буду вдячний.

ПС: Я пробував через бібліотеку 'com.microsoft.azure:azure-cosmosdb:jar:2.6.5' але не можу зєднатись таким способом чомусь, видає 400 BadRequest, url до БД і MasterKey коректні.

1 лайк

Это международный портал и кажется с большей вероятностью ответят, если текст будет на русском языке :slight_smile:

Привет. Покажу, как я это реализовал, буду рад вашим комментариям.

Клас, где создается коннект к БД, клиент к таблице ну и собственно мэтод, который делает запрос.

public class TableRequest {
   private Logger logger = LoggerFactory.getLogger(TableRequest.class);
   private CloudTableClient currentClient;

   public TableRequest() throws URISyntaxException, InvalidKeyException {
       currentClient = createClient();
       logger.info("New connection to Cosmos DB created");
   }

   public <T extends TableEntity> Iterable<T> select(String table, TableQuery<T> query) throws URISyntaxException, StorageException {
       CloudTable cloudTable = currentClient.getTableReference(table);
       logger.info("CloudTableClient created");
       cloudTable.createIfNotExists();
       return cloudTable.execute(query);
   }

   private CloudTableClient createClient() throws URISyntaxException, InvalidKeyException {
       CloudStorageAccount cloudStorageAccount = CloudStorageAccount.parse(System.getProperty(COSMOS_DB_CONFIG));
       logger.info("Cloud storage account created from the connection string");
       CloudTableClient tableClient = cloudStorageAccount.createCloudTableClient();
       logger.info("Created a new Table service client");
       return tableClient;
   }
}

Клас, где создается сам запрос

public class Query {
    private static Logger logger = LoggerFactory.getLogger(Query.class);
    public static TableQuery<SomeTableEntity> getAssetSometInfo(String columnName, String operation, String value){
        TableQuery<SomeTableEntity> query = new TableQuery<SomeTableEntity>()
                .where(TableQuery.generateFilterCondition(
                        columnName,
                        operation,
                        value)
                );
        logger.info("Query created -> " + query.getFilterString());
        query.setClazzType(SomeTableEntity.class);
        return query;
    }
}

Клас, который есть объектом полей в БД (пример)

public class SomeTableEntity extends TableServiceEntity {
    private String id;
    private String type;

    public String getId() {
        return id;
    }

    public void setId(String id;{
        this.id; = id;
    }

    public String getType;() {
        return type;
    }

    public void setType(String type;{
        this.type; = type;
    }
}

В самом тесте код смотрится так:

private void testSomeDataAfterSomething(String type, String id) throws URISyntaxException, StorageException {
        Iterable<SomeTableEntity> iterable = tableRequest.select(Table.SOME.getName(),
                Query.getAssetEventInfo(
                        "Id",
                        TableQuery.QueryComparisons.EQUAL,
                        id));
        logger.info("Select was done");

        List<SomeTableEntity> someList = StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList());
        SomeTableEntityevent some = someList .get(someList .size() - 1);
        assertEquals("Incorrect SomeId with id " + id, id, some.getId());
        assertEquals("Incorrect SomeType with id " + id, type, some.getType());
        
}

Если нужно проверить много данных (не последнюю запись в БД)

AtomicInteger counter = new AtomicInteger(0);
someList .forEach(some -> {
            int d = counter.getAndIncrement();
            assertEquals("Incorrect SomeId with id " + id, id, some.getId());
            assertEquals("Incorrect SomeType with id " + id, type, some.getType());
            });

Возникли такие проблемы:

  1. Тести запускаются параллельно в несколько потоков, соответственно создается n-потокв конекшинов к БД. У нас cucumber, коннект к БД создаю в классе со степами. Использовал аннотацию @Before, чтобы перед запуском Feature файла создавался коннект, но это происходит в каждом потоке (С cucumber пока на очень на Вы) :
private TableRequest tableRequest;

@Before("@FEATURENAME")
public void connectToCosmosDB() throws URISyntaxException, InvalidKeyException {
        tableRequest = new TableRequest();
    }

FEATURENAME - это файл с сценариями

@FEATURENAME 
Feature: Feature name
  1. Запроси в БД происходят раньше записи, пришлось написать класс c медом RETRY
    Проблема решилась. Тут несколько примеров - первый, второй