Статьи

Использование MongoDB со Sculptor

В этой статье описывается то, что Sculptor предоставляет для эффективной разработки корпоративного или веб-приложения с богатой доменной моделью, хранящейся в MongoDB . MongoDB устраняет разрыв между хранилищами значений ключей (которые являются быстрыми и легко масштабируемыми) и традиционными системами RDBMS (которые предоставляют богатые запросы и широкие функциональные возможности).

Sculptor — это продуктивный инструмент, который позволяет вам выразить свои замыслы в текстовом DSL, из которого Sculptor генерирует высококачественный код и конфигурацию Java.

Sculptor создает классы отображения данных, которые преобразуют доменные объекты в / из структур данных MongoDB, DBObjects. Это упрощает использование модели предметной области в виде DDD с автоматическим сопоставлением со структурами данных MongoDB.

Sculptor предоставляет общие операции с репозиториями для использования с MongoDB. Это включает в себя такие операции, как сохранение, удаление, findById, findByKey, findByCondition и некоторые другие. Вы получаете операции CRUD и GUI бесплатно.

Запросы могут быть выражены с помощью плавного API, поддерживающего завершение кода и рефакторинг.

Богатая поддержка ассоциаций. Агрегаты хранятся в виде встроенных документов. Другие ассоциации хранятся со ссылочными идентификаторами. В доменных объектах генерируются геттеры, которые лениво выбирают связанные объекты из идентификаторов. Это означает, что вам не нужно работать с идентификаторами самостоятельно, вы можете следить за ассоциациями, как обычно.

Пример блогового приложения может выглядеть следующим образом, если он определен в текстовом DSL Sculptor:

Визуализация, созданная Sculptor, приведенной выше модели выглядит следующим образом:

Data Mapper

При работе с Java API mongoDB DBObject играет центральную роль. Это как ключевое значение Map. Значения могут быть большинства типов, а также коллекций и других объектов DBO для вложенных документов. В Sculptor также добавлена ​​поддержка классов даты и времени Enums и Joda.

Реализация отображения доменных объектов в DBObjects — утомительная и подверженная ошибкам задача программирования, которая является идеальным кандидатом для автоматизации. Скульптор пригодится для этого. Sculptor генерирует классы отображения данных, которые преобразуют доменные объекты в объекты DBObject. Полезно не писать эти картографы вручную.

        Entity BlogPost {
            String slug key
            String title
            String body
        }

Исходя из этого, Sculptor генерирует класс объектов домена в Java с соответствующими методами получения, установки, конструктора, равенства и т. Д. Sculptor также создает преобразователь для преобразования BlogPost в / из DBObject. Сгенерированный код выглядит так:

    public DBObject toData(BlogPost from) {
        if (from == null) {
            return null;
        }

        DBObject result = new BasicDBObject();

        if (from.getId() != null) {
            ObjectId objectId = ObjectId.massageToObjectId(from.getId());
            result.put("_id", objectId);
        }

        result.put("slug", from.getSlug());
        result.put("title", from.getTitle());
        result.put("body", from.getBody());

        return result;
    }

и в другом направлении:

    public BlogPost toDomain(DBObject from) {
        if (from == null) {
            return null;
        }

        String slug = (String) from.get("slug");

        BlogPost result = new BlogPost(slug);

        if (from.containsField("_id")) {
            ObjectId objectId = (ObjectId) from.get("_id");
            String idString = objectId.toStringMongod();
            IdReflectionUtil.internalSetId(result, idString);
        }

        if (from.containsField("title")) {
            result.setTitle((String) from.get("title"));
        }
        if (from.containsField("body")) {
            result.setBody((String) from.get("body"));
        }

        return result;
    }

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

Объекты домена, конечно, также могут иметь поведение, иначе это не будет моделью с расширенным доменом. Логика поведения всегда пишется вручную. Разделение сгенерированного и написанного вручную кода выполняется сгенерированным базовым классом и написанным вручную подклассом, классом пробела. Именно в подкласс вы добавляете методы для реализации поведения объекта домена. Подкласс также генерируется, но только один раз, он никогда не будет перезаписан генератором.

Возможно, вам придется добавить некоторый рукописный код для настройки картографов. Это просто. В модели вы можете указать, что вам нужен подкласс, который вы можете реализовать самостоятельно. Это очень полезно для переноса данных.

Также может быть полезно знать, что поля, помеченные как переходные, не сохраняются, но загружаются, если они существуют в извлеченных документах. Это также может быть использовано для переноса данных.

По умолчанию имена в хранилище данных совпадают с именами в объектах домена Java. В случае, если вам нужно использовать другие имена, это можно определить в модели с databaseTable и databaseColumn.

        Entity BlogPost {
            databaseTable="BlogEntries"
            String slug key
            String title
            String body databaseColumn="content"
        }

Вы, вероятно, выбрали MongoDB за его низкую задержку. Тогда вы хотите, чтобы картограф данных также был быстрым. Сопоставители, предоставляемые Sculptor, представляют собой сгенерированный код, который работает на полной скорости. Альтернативные решения с использованием отражения или промежуточного формата String JSON, вероятно, не так быстро.

ассоциации

MongoDB не является реляционной базой данных, и нет такой вещи, как объединения. Вы все еще можете использовать ассоциации между объектами домена. Ассоциации могут быть двух основных категорий: встроенные или ссылки по идентификатору.

сводные показатели

Агрегаты являются одним из основных строительных блоков в доменно-управляемом дизайне (DDD). MongoDB имеет отличную поддержку агрегатов, поскольку связанный объект может храниться как внедренный документ, то есть он принадлежит родительскому объекту и не может использоваться несколькими объектами.

Давайте повторим, что DDD говорит о агрегатах:


Агрегат — это группа связанных объектов, которые рассматриваются как одна единица в отношении изменений данных.
Агрегат разграничен границей, которая отделяет объекты внутри от внешних. Каждый агрегат имеет один корень. Корень — это сущность, и это единственный объект, доступный извне. Корень может содержать ссылки на любой из агрегатных объектов, а другие объекты могут содержать ссылки друг на друга, но внешний объект может содержать ссылки только на корневой объект. — от DDD Быстро

Скульптор проверит ссылочные ограничения, описанные в цитате выше. Репозитории доступны только для совокупных корней. Агрегаты определяются с помощью атрибутамигранта или нет в принадлежащих ему доменных объектах.

Типичным агрегатом в образце блога является то, что Комментарий принадлежит BlogPost

        Entity BlogPost {
            String slug key
            String title
            String body
            DateTime published nullable
            - List<comment> comments opposite forPost
        }

        ValueObject Comment {
            not aggregateRoot
            - BlogPost forPost opposite comments
            String title
            String body
        }
</comment>

Ссылка по Id

Другая альтернатива — хранить идентификаторы упомянутых объектов. В доменных объектах генерируются геттеры, которые лениво выбирают связанные объекты из идентификаторов. Это означает, что вам не нужно работать с идентификаторами самостоятельно, вы можете следить за связями, как обычно, но имейте в виду, что при вызове такого получателя может потребоваться запрос к базе данных.

    Set<author> writers = blog.getWriters();
</author>

Таким же образом вы можете изменять несобственные ассоциации, работая с объектами, а не с идентификаторами.

        Author pn = new Author("Patrik");
        pn = authorService.save(getServiceContext(), pn);
        blog.addWriter(pn);
        Author ak = new Author("Andreas");
        ak = authorService.save(getServiceContext(), ak);
        blog.addWriter(ak);
        blogService.save(getServiceContext(), blog);

Ссылочная целостность хранимых идентификаторов не обеспечивается. Это должно быть обработано вашей программой. Ленивые получатели ассоциаций не будут работать, если упомянутый объект отсутствует, они будут возвращать ноль для ссылок на одно значение и игнорируют отсутствующие объекты для ссылок на коллекции. Это означает, что вы можете очистить висячие ссылки, выбирая объекты, заполнять ассоциации, вызывая получатели и затем сохраняя объект.

наследование

Функция MongoDB в Sculptor имеет полную поддержку наследования. Можно даже сделать полиморфные запросы.

        abstract Entity Media {
            String title !changeable

            Repository MediaRepository {
                List<@Media> findByTitle(String title);
                protected findByCondition;
            }
        }

        Entity Book extends @Media {
            String isbn key length="20"
        }

        Entity Movie extends @Media {
            String urlIMDB key
            Integer playLength
            - Genre category nullable
        }

 

вместилище

В приведенном выше примере блога вы, возможно, заметили, что есть службы с findById, findAll, save и delete, даже если они не были явно определены в модели. Они приходят от объектов домена помечаются с эшафота . Это автоматически генерирует некоторые предопределенные операции CRUD в репозитории и соответствующей службе.

    Entity BlogPost {
            scaffold
            - Blog inBlog
            String slug key
            String title
            String body
            DateTime published nullable
            Set<String> tags
            - Author writtenBy
            - List<Comment> comments opposite forPost
    }

Вы также можете явно определить необходимые операции следующим образом:

    Entity BlogPost {
            - Blog inBlog
            String slug key
            String title
            String body
            DateTime published nullable
            Set<String> tags
            - Author writtenBy
            - List<Comment> comments opposite forPost

            Repository BlogPostRepository { 
                save;
                delete;
                findAll(PagingParameter pagingParameter);
                findById;
                findByKey;
                List<@BlogPost> findPostsInBlog(@Blog blog);
                List<@BlogPost> findPostsWithComments => AccessObject;
                List<@BlogPost> findPostsWithTags(Set<String> tags);
                protected findByCondition;
            }

Вам не нужно делать никакого ручного кодирования для встроенных операций, таких как save, delete, findAll, findById, findByKey. findByKey для естественного ключа, то есть атрибутов, помеченных ключом (см. выше).

Есть хорошая поддержка для нумерации страниц и сортировки.

Как видно из приведенного выше примера, также можно определить свои собственные операции с репозиторием, например findPostsWithTags.

findByCondition

Самый интересный искатель — findByCondition. Запросы могут быть выражены с помощью внутреннего DSL, который поддерживает завершение кода и рефакторинг. Лучше всего это проиллюстрировать несколькими образцами.

    public List<BlogPost> findPostsInBlog(Blog blog) {
        List<ConditionalCriteria> condition = criteriaFor(BlogPost.class)
            .withProperty(inBlog()).eq(blog)
            .orderBy(published()).descending()
            .build();
        return findByCondition(condition);
    }
    public List<BlogPost> findPostsWithGreatComments() {
        List<ConditionalCriteria> condition = criteriaFor(BlogPost.class)
            .withProperty(comments().title()).ignoreCaseLike(".*great.*")
            .and().withProperty(published()).isNotNull()
            .orderBy(published()).descending().build();
        return findByCondition(condition);
    }

ConditionalCriteriaBuilder имеет поддержку определения запросов с помощью eq, например, lessThan, moreThan, in, а не orderBy и некоторые другие.

Конечно, вы также можете напрямую работать с лежащим в основе MongoDB DBCollection и DBObject для выполнения сложных запросов, которые могут не поддерживаться findByCondition.

Попытайся

Дополнительные инструкции о том, как использовать Sculptor, доступны в Учебном пособии по Sculptor MongoDB .