{"id":124153,"date":"2026-06-09T10:00:00","date_gmt":"2026-06-09T10:00:00","guid":{"rendered":"https:\/\/foojay.io\/?p=124153"},"modified":"2026-06-08T16:04:33","modified_gmt":"2026-06-08T16:04:33","slug":"introduction-to-cqrs-using-mongodb","status":"publish","type":"post","link":"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/","title":{"rendered":"Introduction to CQRS using MongoDB"},"content":{"rendered":"\n    <div class=\"article__table\">\n        <div class=\"article__table-header\">\n            <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                <path d=\"M8 6H21\" stroke=\"#3562E5\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" \/>\n                <path d=\"M8 12H21\" stroke=\"#3562E5\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" \/>\n                <path d=\"M8 18H21\" stroke=\"#3562E5\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" \/>\n                <path d=\"M3 6H3.01\" stroke=\"#3562E5\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" \/>\n                <path d=\"M3 12H3.01\" stroke=\"#3562E5\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" \/>\n                <path d=\"M3 18H3.01\" stroke=\"#3562E5\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" \/>\n            <\/svg>\n            Table of Contents\n            <svg class=\"chevron\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                <path d=\"M18 15L12 9L6 15\" stroke=\"#3562E5\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/>\n            <\/svg>\n        <\/div>\n        <div class=\"article__table-body\"><span><a href=\"#h2-0--rerequisites\">Prerequisites<\/a><\/span><span><a href=\"#h2-1--tep-1-reate-the-entities\">Step 1: Create the entities<\/a><\/span><span><a href=\"#h2-2--tep-2-reating-ommand\">Step 2: Creating Command<\/a><\/span><span><a href=\"#h2-3--tep-3-reate-uery\">Step 3: Create Query<\/a><\/span><span><a href=\"#h2-4--onclusion\">Conclusion<\/a><\/span><\/div><\/div><!DOCTYPE html PUBLIC \"-\/\/W3C\/\/DTD HTML 4.0 Transitional\/\/EN\" \"http:\/\/www.w3.org\/TR\/REC-html40\/loose.dtd\">\n<?xml encoding=\"utf-8\" ?><html><body><p>In enterprise environments, projects often begin with a simple structure: one model, one service, and one document, using a single class and data transfer object for both read and write operations. While this unified approach works at first, it becomes problematic as requirements grow. Operations become more complex, requiring additional validations, rules, and constraints. Over time, read operations may demand different formats, such as aggregations, summaries, or custom views. Relying on a single model for both reading and writing leads to maintenance challenges and inefficient queries. This approach can result in returning unnecessary data or omitting required information, violating the single responsibility principle and making the design less effective.<\/p>\n\n\n\n<p>The distinction between read and write operations becomes increasingly important as their goals diverge. Command Query Responsibility Segregation (<strong>CQRS<\/strong>) addresses this by separating read (Query) and write (Command) operations into distinct models. This architectural pattern allows each model to be optimized, scaled, and secured independently, reducing conflicts and improving overall system efficiency.<\/p>\n\n\n\n<p>&#8203;<\/p>\n\n\n\n<p>This tutorial introduces the CQRS architectural pattern and demonstrates how to implement it with <a target=\"_blank\" href=\"https:\/\/www.mongodb.com\/?utm_campaign=devrel&amp;utm_source=third-party-content&amp;utm_medium=cta&amp;utm_term=hugh.murray\">MongoDB<\/a>.<\/p>\n\n\n\n<p>In this tutorial, you&rsquo;ll:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Model a simple payment system.<\/li>\n\n\n\n<li>Model and interact with MongoDB using Java.<\/li>\n\n\n\n<li>Explore how MongoDB can help you achieve a CQRS design.<\/li>\n<\/ul>\n\n\n\n<p>You can find all the code presented in this tutorial in the <a target=\"_blank\" href=\"https:\/\/github.com\/soujava\/helidon-mongodb-cqrs\">GitHub repository<\/a>:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">git clone git@github.com:soujava\/helidon-mongodb-cqrs.git<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h2-0--rerequisites\">Prerequisites<\/h2>\n\n\n\n<p>For this tutorial, you&rsquo;ll need:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Java 21.<\/li>\n\n\n\n<li>Maven.<\/li>\n\n\n\n<li>A MongoDB cluster.\n<ul class=\"wp-block-list\">\n<li><a target=\"_blank\" href=\"https:\/\/www.mongodb.com\/cloud\/atlas\/register?utm_campaign=devrel&amp;utm_source=third-party-content&amp;utm_medium=cta&amp;utm_content=data_driven_test_dev&amp;utm_term=otavio.santana\">MongoDB Atlas<\/a> (Option 1)<\/li>\n\n\n\n<li>Docker (Option 2)<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p>You can use the following Docker command to start a standalone MongoDB instance:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"dockerfile\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">docker run --rm -d --name mongodb-instance -p 27017:27017 mongo<\/pre>\n\n\n\n<p>By decoupling read and write operations, CQRS assigns each its own responsibility. Instead of using one model to manage both state changes and data retrieval, CQRS separates these into commands and queries. Commands represent actions and enforce business rules, while queries retrieve information from purpose-built data models. This separation avoids conflicts and allows each side to evolve independently.<\/p>\n\n\n\n<p>This tutorial focuses on a specific, instructional use case: authorizing card usage. Instead of building a complete financial system, we concentrate on authorizing transactions and retrieving transaction history. This limited scope helps demonstrate the process of creating both command and query components.To maintain consistency with the previous post, we will use <a target=\"_blank\" href=\"https:\/\/www.mongodb.com\/community\/forums\/t\/introduction-to-mongodb-and-helidon\/303061\/?utm_campaign=devrel&amp;utm_source=third-party-content&amp;utm_medium=cta&amp;utm_term=hugh.murray\">Helidon with MicroProfile<\/a>. You may define the groupId, artifactId, version, and package name as you prefer. After downloading the project, include the MongoDB integration&mdash;Eclipse JNoSQL&mdash;in the pom.xml file at the project root:<\/p>\n\n\n\n<p><\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"540\" src=\"https:\/\/foojay.io\/wp-content\/uploads\/2026\/06\/mon1-1024x540.png\" alt=\"\" class=\"wp-image-124155\" srcset=\"https:\/\/foojay.io\/wp-content\/uploads\/2026\/06\/mon1-1024x540.png 1024w, https:\/\/foojay.io\/wp-content\/uploads\/2026\/06\/mon1-700x369.png 700w, https:\/\/foojay.io\/wp-content\/uploads\/2026\/06\/mon1-768x405.png 768w, https:\/\/foojay.io\/wp-content\/uploads\/2026\/06\/mon1.png 1529w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>To maintain consistency <a target=\"_blank\" href=\"https:\/\/dev.to\/mongodb\/introduction-to-events-using-mongodb-22b4\">with the previous post<\/a>, we will use <a target=\"_blank\" href=\"https:\/\/helidon.io\/starter\/4.4.1\">Helidon<\/a> with MicroProfile. You may define the groupId, artifactId, version, and package name as you prefer. After downloading the project, include the MongoDB integration&mdash;Eclipse JNoSQL&mdash;in the pom.xml file at the project root:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"xml\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;dependency&gt;\n   &lt;groupId&gt;org.eclipse.jnosql.databases&lt;\/groupId&gt;\n   &lt;artifactId&gt;jnosql-mongodb&lt;\/artifactId&gt;\n   &lt;version&gt;1.1.13&lt;\/version&gt;\n&lt;\/dependency&gt;<\/pre>\n\n\n\n<p>With the project defined, the next step is to set the database configuration. You can either use a local database or explore MongoDB Atlas; either is fine. We will use locally, thus, run this Docker command to start a MongoDB instance:<\/p>\n\n\n\n<p>Include those new properties:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># configure the MongoDB client for a replica set of two nodes\njnosql.mongodb.url=mongodb+srv:\/\/admin:&lt;db_password&gt;@cluster0.gblhb3d.mongodb.net\/?appName=devrel-article-java-jnosql\n# mandatory define the database name\njnosql.document.database=cards\njnosql.mongodb.application.name=devrel-article-java-jnosql<\/pre>\n\n\n\n<p>PRO TIP: MongoDB Atlas is a valuable Database-as-a-Service option. It simplifies operations by delegating database management to MongoDB experts.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h2-1--tep-1-reate-the-entities\">Step 1: Create the entities<\/h2>\n\n\n\n<p>The first step is to create the necessary entities. We need entities for managing status and a separate entity for queries, which can serve as an aggregator or summary. On the command side, we define two entities: Card, which holds the current status and available amount, and OperationResult, which records each card operation attempt. OperationResult is immutable and cannot be changed once it is stored in the database.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.acme.cards.command;\n\nimport com.acme.cards.infraestructure.JsonFieldStrategy;\nimport jakarta.json.bind.annotation.JsonbVisibility;\nimport jakarta.nosql.Column;\nimport jakarta.nosql.Entity;\nimport jakarta.nosql.Id;\n\nimport java.math.BigDecimal;\nimport java.util.Objects;\nimport java.util.UUID;\n\n@Entity\n@JsonbVisibility(JsonFieldStrategy.class)\npublic class Card {\n\n    @Id\n    private UUID id;\n\n    @Column\n    private BigDecimal availableBalance;\n\n    @Column\n    private CardOperationStatus status;\n\n    Card() {\n    }\n\n    public Card(UUID id, BigDecimal availableBalance, CardOperationStatus status) {\n        this.id = id;\n        this.availableBalance = availableBalance;\n        this.status = status;\n    }\n\n    public UUID getId() {\n        return id;\n    }\n\n    public BigDecimal getAvailableBalance() {\n        return availableBalance;\n    }\n\n    public CardOperationStatus getStatus() {\n        return status;\n    }\n\n    \/**\n     * Determines whether a card can authorize a transaction for the given amount.\n     * The authorization is possible only if the card is active and the available\n     * balance is greater than or equal to the specified amount.\n     *\n     * @param amount the transaction amount to be authorized\n     * @return true if the card is active and has enough available balance\n     *         to authorize the transaction, false otherwise\n     *\/\n    public boolean canAuthorize(BigDecimal amount) {\n        return status == CardOperationStatus.ACTIVE\n                &amp;&amp; availableBalance.compareTo(amount) &gt;= 0;\n    }\n\n    \/**\n     * Deducts the specified amount from the available balance of the card.\n     *\n     * @param amount the amount to be debited from the card's available balance\n     *\/\n    public void debit(BigDecimal amount) {\n        this.availableBalance = this.availableBalance.subtract(amount);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (!(o instanceof Card card)) {\n            return false;\n        }\n        return Objects.equals(id, card.id);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hashCode(id);\n    }\n\n    @Override\n    public String toString() {\n        return \"Card{\" +\n                \"id=\" + id +\n                \", availableBalance=\" + availableBalance +\n                \", cardOperationStatus=\" + status +\n                '}';\n    }\n}<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.acme.cards.command;\n\nimport jakarta.nosql.Column;\nimport jakarta.nosql.Entity;\nimport jakarta.nosql.Id;\n\nimport java.time.Instant;\nimport java.util.UUID;\n\n@Entity\npublic record OperationResult(@Id UUID id,\n                              @Column UUID cardId,\n                              @Column OperationStatus status,\n                              @Column String reason,\n                              @Column Instant processedAt) {\n}<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.acme.cards.command;\n\npublic enum CardOperationStatus {\n    ACTIVE, BLOCKED\n}<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.acme.cards.command;\n\npublic enum OperationStatus {\n    APPROVED, DECLINED\n}<\/pre>\n\n\n\n<p>After creating the command operations, the next step is to define the entity where we will store the transactions and operations. This entity will define where the user will read. This one is where we will process the data and make it available to the read operations.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.acme.cards.query;\n\nimport jakarta.nosql.Column;\nimport jakarta.nosql.Entity;\nimport jakarta.nosql.Id;\n\nimport java.math.BigDecimal;\nimport java.time.Instant;\nimport java.util.UUID;\n\n@Entity\npublic record TransactionView(@Id UUID id,\n                              @Column UUID cardId,\n                              @Column BigDecimal amount,\n                              @Column String status,\n                              @Column Instant createdAt) {\n}<\/pre>\n\n\n\n<p>To simplify the structure, we will create a REST API to generate cards. Based on these cards, we will handle debit operations and check if a card has sufficient available balance.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.acme.cards.command;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport jakarta.ws.rs.*;\nimport jakarta.ws.rs.core.MediaType;\n\nimport jakarta.ws.rs.core.Response;\nimport org.eclipse.jnosql.mapping.document.DocumentTemplate;\n\nimport java.math.BigDecimal;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.UUID;\nimport java.util.logging.Logger;\nimport java.util.stream.IntStream;\n\n@Path(\"\/cards\")\n@Produces(MediaType.APPLICATION_JSON)\n@Consumes(MediaType.APPLICATION_JSON)\n@ApplicationScoped\npublic class CardResource {\n\n    private static final Logger LOGGER = Logger.getLogger(CardResource.class.getName());\n    private static final BigDecimal INITIAL_BALANCE = new BigDecimal(\"1000\");\n\n    private final DocumentTemplate template;\n\n    @Inject\n    public CardResource(DocumentTemplate template) {\n        this.template = template;\n    }\n\n    CardResource() {\n        this.template = null;\n    }\n\n    @GET\n    public List&lt;Card&gt; findAll() {\n\n        LOGGER.info(\"Fetching all cards\");\n\n        List&lt;Card&gt; cards = template.select(Card.class).result();\n        if (cards.isEmpty()) {\n            LOGGER.warning(\"No cards found. Seeding initial dataset...\");\n            cards = generateCards();;\n        }\n        LOGGER.info(\"Returning \" + cards.size() + \" cards\");\n        return cards;\n    }\n\n    @GET\n    @Path(\"\/{id}\")\n    public Card findById(@PathParam(\"id\") UUID id) {\n\n        LOGGER.info(\"Fetching card with id=\" + id);\n\n        return template.find(Card.class, id).orElseThrow(() -&gt;\n                   new WebApplicationException(\"Card not found with the id: \" + id, Response.Status.NOT_FOUND)\n                );\n    }\n\n    private List&lt;Card&gt; generateCards() {\n        List&lt;Card&gt; cards = new ArrayList&lt;&gt;(); ;\n\n        IntStream.range(0, 5)\n                .mapToObj(i -&gt; new Card(\n                        UUID.randomUUID(),\n                        INITIAL_BALANCE,\n                        CardOperationStatus.ACTIVE\n                ))\n                .forEach(card -&gt; {\n                    cards.add(template.insert(card));\n                    LOGGER.fine(\"Seeded card with id=\" + card.getId()\n                            + \" and balance=\" + card.getAvailableBalance());\n                });\n\n        LOGGER.info(\"Finished seeding cards + \" + cards.size() + \" cards created\");\n        return cards;\n    }\n}<\/pre>\n\n\n\n<p>With this structure, we can now work with cards. In a real-world scenario, additional card statuses such as frozen or canceled would be included. Here, we focus on a small part of the problem to highlight the architectural pattern.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h2-2--tep-2-reating-ommand\">Step 2: Creating Command<\/h2>\n\n\n\n<p>The next step is to create the command responsible for write operations. Here, we use the AuthorizeCardCommand, which encapsulates the attributes needed for card operations. This approach avoids handling numerous parameters by using a single class.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.acme.cards.command;\n\nimport java.math.BigDecimal;\nimport java.util.UUID;\n\npublic record AuthorizeCardCommand(UUID cardId, BigDecimal amount, String reason) {\n}<\/pre>\n\n\n\n<p>The next class is the handler responsible for processing authorizations. When a debit is requested, it checks if sufficient funds are available. If so, it processes the transaction and updates the TransactionView, as addressed in the query section.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.acme.cards.command;\n\nimport com.acme.cards.query.TransactionView;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\n\nimport jakarta.ws.rs.WebApplicationException;\nimport org.eclipse.jnosql.mapping.document.DocumentTemplate;\n\nimport java.time.Instant;\nimport java.util.UUID;\nimport java.util.logging.Logger;\n\nimport static jakarta.ws.rs.core.Response.Status.NOT_FOUND;\n\n@ApplicationScoped\npublic class AuthorizeCardCommandHandler {\n\n    private static final Logger LOGGER = Logger.getLogger(AuthorizeCardCommandHandler.class.getName());\n\n    private final DocumentTemplate template;\n\n    @Inject\n    public AuthorizeCardCommandHandler(DocumentTemplate template) {\n        this.template = template;\n    }\n\n    AuthorizeCardCommandHandler() {\n        this.template = null;\n    }\n\n    public OperationResult handle(AuthorizeCardCommand command) {\n\n        LOGGER.info(\"Processing authorize command for cardId=\" + command.cardId()\n                + \" amount=\" + command.amount());\n\n        var card = template.find(Card.class, command.cardId())\n                .orElseThrow(() -&gt; new WebApplicationException(\"Card not found, cardid=\" + command.cardId(), NOT_FOUND));\n\n        var operationId = UUID.randomUUID();\n\n        if (!card.canAuthorize(command.amount())) {\n            LOGGER.warning(\"Authorization declined for cardId=\" + command.cardId());\n\n            var result = new OperationResult(\n                    operationId,\n                    card.getId(),\n                    OperationStatus.DECLINED,\n                    \"Insufficient balance or inactive card\",\n                    Instant.now()\n            );\n\n            template.insert(result);\n\n            return result;\n        }\n\n        card.debit(command.amount());\n        template.update(card);\n\n        LOGGER.info(\"Authorization approved for cardId=\" + command.cardId());\n\n        var result = new OperationResult(\n                operationId,\n                card.getId(),\n                OperationStatus.APPROVED,\n                command.reason(),\n                Instant.now()\n        );\n\n        template.insert(result);\n        updateProjection(command, result);\n\n        return result;\n    }\n\n    private void updateProjection(AuthorizeCardCommand command, OperationResult result) {\n\n        LOGGER.info(\"Updating transaction view for id=\" + result.id());\n\n        var view = new TransactionView(\n                result.id(),\n                command.cardId(),\n                command.amount(),\n                result.status().name(),\n                result.processedAt()\n        );\n\n        template.insert(view);\n    }\n}<\/pre>\n\n\n\n<p>Finally, we define the resource that initiates the command operation:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.acme.cards.command;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport jakarta.ws.rs.*;\nimport jakarta.ws.rs.core.MediaType;\n\nimport java.util.UUID;\nimport java.util.logging.Logger;\n\n@Path(\"\/cards\/{id}\/authorize\")\n@Produces(MediaType.APPLICATION_JSON)\n@Consumes(MediaType.APPLICATION_JSON)\n@ApplicationScoped\npublic class CardCommandResource {\n\n    private static final Logger LOGGER = Logger.getLogger(CardCommandResource.class.getName());\n\n    private final AuthorizeCardCommandHandler handler;\n\n    @Inject\n    public CardCommandResource(AuthorizeCardCommandHandler handler) {\n        this.handler = handler;\n    }\n\n    CardCommandResource() {\n        this.handler = null;\n    }\n\n    @POST\n    public OperationResult authorize(@PathParam(\"id\") UUID cardId, AuthorizeRequest request) {\n\n        LOGGER.info(\"Received authorize request for cardId=\" + cardId);\n\n        var command = new AuthorizeCardCommand(cardId, request.amount(), request.reason());\n        return handler.handle(command);\n    }\n\n}<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h2-3--tep-3-reate-uery\">Step 3: Create Query<\/h2>\n\n\n\n<p>The final step is to create the query resource. While the command writes transactions, the query retrieves processed transactions for a given card.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.acme.cards.query;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport jakarta.ws.rs.*;\nimport jakarta.ws.rs.core.MediaType;\n\nimport org.eclipse.jnosql.mapping.document.DocumentTemplate;\n\nimport java.util.List;\nimport java.util.UUID;\nimport java.util.logging.Logger;\n\n@Path(\"\/cards\/{id}\/transactions\")\n@Produces(MediaType.APPLICATION_JSON)\n@ApplicationScoped\npublic class CardQueryResource {\n\n    private static final Logger LOGGER = Logger.getLogger(CardQueryResource.class.getName());\n\n    private final DocumentTemplate template;\n\n    @Inject\n    public CardQueryResource(DocumentTemplate template) {\n        this.template = template;\n    }\n\n    CardQueryResource() {\n        this.template = null;\n    }\n\n    @GET\n    public List&lt;TransactionView&gt; findByCardId(@PathParam(\"id\") UUID cardId) {\n\n        LOGGER.info(\"Fetching transactions for cardId=\" + cardId);\n\n        return template.select(TransactionView.class)\n                .where(\"cardId\")\n                .eq(cardId)\n                .orderBy(\"createdAt\")\n                .desc()\n                .result();\n    }\n}<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h2-4--onclusion\">Conclusion<\/h2>\n\n\n\n<p>CQRS is not simply about dividing code for architectural reasons. It recognizes that reads and writes have distinct purposes. Separating commands from queries allows each to focus on its role: commands enforce business rules and generate decisions, while queries deliver optimized views of the system&rsquo;s state. In this tutorial, we demonstrated this approach using a basic card authorization flow, highlighting the difference between a decision (OperationResult) and the data used for reading (TransactionView).<\/p>\n\n\n\n<p>This example uses a limited scope to clarify the concept. CQRS is not a default choice, but a response to complexity, particularly when read and write requirements diverge. By understanding this separation in a focused scenario, you can better assess when CQRS is valuable and apply it where it has the most impact.<\/p>\n\n\n\n<p>Ready to explore the benefits of MongoDB Atlas? Get started now by <a target=\"_blank\" href=\"https:\/\/www.mongodb.com\/lp\/cloud\/atlas\/try4-reg?utm_campaign=devrel&amp;utm_source=third-party-content&amp;utm_medium=cta&amp;utm_content=data_driven_test_dev&amp;utm_term=otavio.santana\">trying MongoDB Atlas<\/a>.<\/p>\n\n\n\n<p><a target=\"_blank\" href=\"https:\/\/github.com\/soujava\/helidon-mongodb-cqrs\">Access the source code<\/a> used in this tutorial. Any questions? Come chat with us in the <a target=\"_blank\" href=\"https:\/\/www.mongodb.com\/community\/forums\/?utm_campaign=devrel&amp;utm_source=third-party-content&amp;utm_medium=cta&amp;utm_content=data_driven_test_dev&amp;utm_term=otavio.santana\">MongoDB Community Forum<\/a>.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><strong>References<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a target=\"_blank\" href=\"https:\/\/github.com\/soujava\/helidon-mongodb-cqrs\">Source code<\/a><\/li>\n<\/ul>\n<\/body><\/html>\n","protected":false},"excerpt":{"rendered":"<p>Table of Contents PrerequisitesStep 1: Create the entitiesStep 2: Creating CommandStep 3: Create QueryConclusion In enterprise environments, projects often begin with a simple structure: one model, one service, and one document, using a single class and data transfer object for &#8230;<\/p>\n","protected":false},"author":709,"featured_media":124157,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[2023,33,1417],"class_list":["post-124153","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-cqrs","tag-java","tag-mongodb"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Introduction to CQRS using MongoDB<\/title>\n<meta name=\"description\" content=\"In enterprise environments, projects often begin with a simple structure: one model, one service, and one document, using a single class and data transfer object for both read and write operations. While this unified approach works at first, it becomes problematic as requirements grow. Operations become more complex, requiring additional validations, rules, and constraints. Over time, read operations may demand different formats, such as aggregations, summaries, or custom views. Relying on a single model for both reading and writing leads to maintenance challenges and inefficient queries. This approach can result in returning unnecessary data or omitting required information, violating the single responsibility principle and making the design less effective.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Introduction to CQRS using MongoDB\" \/>\n<meta property=\"og:description\" content=\"In enterprise environments, projects often begin with a simple structure: one model, one service, and one document, using a single class and data transfer object for both read and write operations. While this unified approach works at first, it becomes problematic as requirements grow. Operations become more complex, requiring additional validations, rules, and constraints. Over time, read operations may demand different formats, such as aggregations, summaries, or custom views. Relying on a single model for both reading and writing leads to maintenance challenges and inefficient queries. This approach can result in returning unnecessary data or omitting required information, violating the single responsibility principle and making the design less effective.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/\" \/>\n<meta property=\"og:site_name\" content=\"foojay\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-09T10:00:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/foojay.io\/wp-content\/uploads\/2026\/06\/Technical_ATLAS_QueryOptimization10x.png\" \/>\n\t<meta property=\"og:image:width\" content=\"720\" \/>\n\t<meta property=\"og:image:height\" content=\"720\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Otavio Santana\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Otavio Santana\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/foojay.io\\\/today\\\/introduction-to-cqrs-using-mongodb\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/foojay.io\\\/today\\\/introduction-to-cqrs-using-mongodb\\\/\"},\"author\":{\"name\":\"Otavio Santana\",\"@id\":\"https:\\\/\\\/foojay.io\\\/#\\\/schema\\\/person\\\/21d5150ed133960692875abf13792632\"},\"headline\":\"Introduction to CQRS using MongoDB\",\"datePublished\":\"2026-06-09T10:00:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/foojay.io\\\/today\\\/introduction-to-cqrs-using-mongodb\\\/\"},\"wordCount\":973,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/foojay.io\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/foojay.io\\\/today\\\/introduction-to-cqrs-using-mongodb\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/foojay.io\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/Technical_ATLAS_QueryOptimization10x.png\",\"keywords\":[\"cqrs\",\"Java\",\"mongoDB\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/foojay.io\\\/today\\\/introduction-to-cqrs-using-mongodb\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/foojay.io\\\/today\\\/introduction-to-cqrs-using-mongodb\\\/\",\"url\":\"https:\\\/\\\/foojay.io\\\/today\\\/introduction-to-cqrs-using-mongodb\\\/\",\"name\":\"Introduction to CQRS using MongoDB\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/foojay.io\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/foojay.io\\\/today\\\/introduction-to-cqrs-using-mongodb\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/foojay.io\\\/today\\\/introduction-to-cqrs-using-mongodb\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/foojay.io\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/Technical_ATLAS_QueryOptimization10x.png\",\"datePublished\":\"2026-06-09T10:00:00+00:00\",\"description\":\"In enterprise environments, projects often begin with a simple structure: one model, one service, and one document, using a single class and data transfer object for both read and write operations. While this unified approach works at first, it becomes problematic as requirements grow. Operations become more complex, requiring additional validations, rules, and constraints. Over time, read operations may demand different formats, such as aggregations, summaries, or custom views. Relying on a single model for both reading and writing leads to maintenance challenges and inefficient queries. This approach can result in returning unnecessary data or omitting required information, violating the single responsibility principle and making the design less effective.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/foojay.io\\\/today\\\/introduction-to-cqrs-using-mongodb\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/foojay.io\\\/today\\\/introduction-to-cqrs-using-mongodb\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/foojay.io\\\/today\\\/introduction-to-cqrs-using-mongodb\\\/#primaryimage\",\"url\":\"https:\\\/\\\/foojay.io\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/Technical_ATLAS_QueryOptimization10x.png\",\"contentUrl\":\"https:\\\/\\\/foojay.io\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/Technical_ATLAS_QueryOptimization10x.png\",\"width\":720,\"height\":720},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/foojay.io\\\/today\\\/introduction-to-cqrs-using-mongodb\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/foojay.io\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Introduction to CQRS using MongoDB\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/foojay.io\\\/#website\",\"url\":\"https:\\\/\\\/foojay.io\\\/\",\"name\":\"foojay\",\"description\":\"a place for friends of OpenJDK\",\"publisher\":{\"@id\":\"https:\\\/\\\/foojay.io\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/foojay.io\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/foojay.io\\\/#organization\",\"name\":\"foojay\",\"url\":\"https:\\\/\\\/foojay.io\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/foojay.io\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/foojay.io\\\/wp-content\\\/uploads\\\/2020\\\/04\\\/cropped-Favicon.png\",\"contentUrl\":\"https:\\\/\\\/foojay.io\\\/wp-content\\\/uploads\\\/2020\\\/04\\\/cropped-Favicon.png\",\"width\":512,\"height\":512,\"caption\":\"foojay\"},\"image\":{\"@id\":\"https:\\\/\\\/foojay.io\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/x.com\\\/foojay2020\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/foojay.io\\\/#\\\/schema\\\/person\\\/21d5150ed133960692875abf13792632\",\"name\":\"Otavio Santana\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/foojay.io\\\/wp-content\\\/uploads\\\/2025\\\/07\\\/1709732663918-96x96.jpeg\",\"url\":\"https:\\\/\\\/foojay.io\\\/wp-content\\\/uploads\\\/2025\\\/07\\\/1709732663918-96x96.jpeg\",\"contentUrl\":\"https:\\\/\\\/foojay.io\\\/wp-content\\\/uploads\\\/2025\\\/07\\\/1709732663918-96x96.jpeg\",\"caption\":\"Otavio Santana\"},\"description\":\"Software Engineer | Software Architect | Speaker | Writer| Book Author | Consultant | Staff Engineer | Mentor | Open Source Committer| Java Champion\",\"sameAs\":[\"https:\\\/\\\/www.linkedin.com\\\/in\\\/otaviojava\\\/\"],\"url\":\"https:\\\/\\\/foojay.io\\\/today\\\/author\\\/otavio-santana\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Introduction to CQRS using MongoDB","description":"In enterprise environments, projects often begin with a simple structure: one model, one service, and one document, using a single class and data transfer object for both read and write operations. While this unified approach works at first, it becomes problematic as requirements grow. Operations become more complex, requiring additional validations, rules, and constraints. Over time, read operations may demand different formats, such as aggregations, summaries, or custom views. Relying on a single model for both reading and writing leads to maintenance challenges and inefficient queries. This approach can result in returning unnecessary data or omitting required information, violating the single responsibility principle and making the design less effective.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/","og_locale":"en_US","og_type":"article","og_title":"Introduction to CQRS using MongoDB","og_description":"In enterprise environments, projects often begin with a simple structure: one model, one service, and one document, using a single class and data transfer object for both read and write operations. While this unified approach works at first, it becomes problematic as requirements grow. Operations become more complex, requiring additional validations, rules, and constraints. Over time, read operations may demand different formats, such as aggregations, summaries, or custom views. Relying on a single model for both reading and writing leads to maintenance challenges and inefficient queries. This approach can result in returning unnecessary data or omitting required information, violating the single responsibility principle and making the design less effective.","og_url":"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/","og_site_name":"foojay","article_published_time":"2026-06-09T10:00:00+00:00","og_image":[{"width":720,"height":720,"url":"https:\/\/foojay.io\/wp-content\/uploads\/2026\/06\/Technical_ATLAS_QueryOptimization10x.png","type":"image\/png"}],"author":"Otavio Santana","twitter_misc":{"Written by":"Otavio Santana","Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/#article","isPartOf":{"@id":"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/"},"author":{"name":"Otavio Santana","@id":"https:\/\/foojay.io\/#\/schema\/person\/21d5150ed133960692875abf13792632"},"headline":"Introduction to CQRS using MongoDB","datePublished":"2026-06-09T10:00:00+00:00","mainEntityOfPage":{"@id":"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/"},"wordCount":973,"commentCount":0,"publisher":{"@id":"https:\/\/foojay.io\/#organization"},"image":{"@id":"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/#primaryimage"},"thumbnailUrl":"https:\/\/foojay.io\/wp-content\/uploads\/2026\/06\/Technical_ATLAS_QueryOptimization10x.png","keywords":["cqrs","Java","mongoDB"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/","url":"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/","name":"Introduction to CQRS using MongoDB","isPartOf":{"@id":"https:\/\/foojay.io\/#website"},"primaryImageOfPage":{"@id":"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/#primaryimage"},"image":{"@id":"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/#primaryimage"},"thumbnailUrl":"https:\/\/foojay.io\/wp-content\/uploads\/2026\/06\/Technical_ATLAS_QueryOptimization10x.png","datePublished":"2026-06-09T10:00:00+00:00","description":"In enterprise environments, projects often begin with a simple structure: one model, one service, and one document, using a single class and data transfer object for both read and write operations. While this unified approach works at first, it becomes problematic as requirements grow. Operations become more complex, requiring additional validations, rules, and constraints. Over time, read operations may demand different formats, such as aggregations, summaries, or custom views. Relying on a single model for both reading and writing leads to maintenance challenges and inefficient queries. This approach can result in returning unnecessary data or omitting required information, violating the single responsibility principle and making the design less effective.","breadcrumb":{"@id":"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/#primaryimage","url":"https:\/\/foojay.io\/wp-content\/uploads\/2026\/06\/Technical_ATLAS_QueryOptimization10x.png","contentUrl":"https:\/\/foojay.io\/wp-content\/uploads\/2026\/06\/Technical_ATLAS_QueryOptimization10x.png","width":720,"height":720},{"@type":"BreadcrumbList","@id":"https:\/\/foojay.io\/today\/introduction-to-cqrs-using-mongodb\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/foojay.io\/"},{"@type":"ListItem","position":2,"name":"Introduction to CQRS using MongoDB"}]},{"@type":"WebSite","@id":"https:\/\/foojay.io\/#website","url":"https:\/\/foojay.io\/","name":"foojay","description":"a place for friends of OpenJDK","publisher":{"@id":"https:\/\/foojay.io\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/foojay.io\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/foojay.io\/#organization","name":"foojay","url":"https:\/\/foojay.io\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/foojay.io\/#\/schema\/logo\/image\/","url":"https:\/\/foojay.io\/wp-content\/uploads\/2020\/04\/cropped-Favicon.png","contentUrl":"https:\/\/foojay.io\/wp-content\/uploads\/2020\/04\/cropped-Favicon.png","width":512,"height":512,"caption":"foojay"},"image":{"@id":"https:\/\/foojay.io\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/foojay2020"]},{"@type":"Person","@id":"https:\/\/foojay.io\/#\/schema\/person\/21d5150ed133960692875abf13792632","name":"Otavio Santana","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/foojay.io\/wp-content\/uploads\/2025\/07\/1709732663918-96x96.jpeg","url":"https:\/\/foojay.io\/wp-content\/uploads\/2025\/07\/1709732663918-96x96.jpeg","contentUrl":"https:\/\/foojay.io\/wp-content\/uploads\/2025\/07\/1709732663918-96x96.jpeg","caption":"Otavio Santana"},"description":"Software Engineer | Software Architect | Speaker | Writer| Book Author | Consultant | Staff Engineer | Mentor | Open Source Committer| Java Champion","sameAs":["https:\/\/www.linkedin.com\/in\/otaviojava\/"],"url":"https:\/\/foojay.io\/today\/author\/otavio-santana\/"}]}},"_links":{"self":[{"href":"https:\/\/foojay.io\/wp-json\/wp\/v2\/posts\/124153","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/foojay.io\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/foojay.io\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/foojay.io\/wp-json\/wp\/v2\/users\/709"}],"replies":[{"embeddable":true,"href":"https:\/\/foojay.io\/wp-json\/wp\/v2\/comments?post=124153"}],"version-history":[{"count":0,"href":"https:\/\/foojay.io\/wp-json\/wp\/v2\/posts\/124153\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/foojay.io\/wp-json\/wp\/v2\/media\/124157"}],"wp:attachment":[{"href":"https:\/\/foojay.io\/wp-json\/wp\/v2\/media?parent=124153"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/foojay.io\/wp-json\/wp\/v2\/categories?post=124153"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/foojay.io\/wp-json\/wp\/v2\/tags?post=124153"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}