Merge pull request #595 from isabiq/master

CQRS pattern
This commit is contained in:
Ilkka Seppälä 2017-07-29 20:45:55 +03:00 committed by GitHub
commit 0982f00a61
21 changed files with 1142 additions and 0 deletions

29
cqrs/README.md Normal file
View File

@ -0,0 +1,29 @@
---
layout: pattern
title: CQRS
folder: cqrs
permalink: /patterns/cqrs/
pumlid: 7SPR4a0m3030gt00pR_RH6I8QQFouFgC_TfHb6gkd5Q7FQBx363ub4rYpoMTZKuDrYXqDX37HIuuyCPfPPTDfuuHREhGqBy0NUR0GNzAMYizMtq1
categories: Architectural
tags:
- Java
- Difficulty-Intermediate
---
## Intent
CQRS Command Query Responsibility Segregation - Separate the query side from the command side.
![alt text](./etc/cqrs.png "CQRS")
## Applicability
Use the CQRS pattern when
* you want to scale the queries and commands independently.
* you want to use different data models for queries and commands. Useful when dealing with complex domains.
* you want to use architectures like event sourcing or task based UI.
## Credits
* [Greg Young - CQRS, Task Based UIs, Event Sourcing agh!](http://codebetter.com/gregyoung/2010/02/16/cqrs-task-based-uis-event-sourcing-agh/)
* [Martin Fowler - CQRS](https://martinfowler.com/bliki/CQRS.html)
* [Oliver Wolf - CQRS for Great Good](https://www.youtube.com/watch?v=Ge53swja9Dw)

BIN
cqrs/etc/cqrs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

115
cqrs/etc/cqrs.ucls Normal file
View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.2.0" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
associations="true" dependencies="false" nesting-relationships="true" router="FAN">
<interface id="1" language="java" name="com.iluwatar.cqrs.commandes.ICommandService" project="cqrs"
file="/cqrs/src/main/java/com/iluwatar/cqrs/commandes/ICommandService.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="291" y="-49"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="2" language="java" name="com.iluwatar.cqrs.commandes.CommandServiceImpl" project="cqrs"
file="/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java" binary="false" corner="BOTTOM_RIGHT">
<position height="263" width="256" x="170" y="87"/>
<display autosize="false" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<interface id="3" language="java" name="com.iluwatar.cqrs.queries.IQueryService" project="cqrs"
file="/cqrs/src/main/java/com/iluwatar/cqrs/queries/IQueryService.java" binary="false" corner="BOTTOM_RIGHT">
<position height="182" width="248" x="176" y="428"/>
<display autosize="false" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="4" language="java" name="com.iluwatar.cqrs.queries.QueryServiceImpl" project="cqrs"
file="/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java" binary="false" corner="BOTTOM_RIGHT">
<position height="258" width="253" x="169" y="665"/>
<display autosize="false" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="5" language="java" name="com.iluwatar.cqrs.domain.model.Book" project="cqrs"
file="/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Book.java" binary="false" corner="BOTTOM_RIGHT">
<position height="326" width="158" x="778" y="-93"/>
<display autosize="false" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="6" language="java" name="com.iluwatar.cqrs.dto.Book" project="cqrs"
file="/cqrs/src/main/java/com/iluwatar/cqrs/dto/Book.java" binary="false" corner="BOTTOM_RIGHT">
<position height="219" width="150" x="541" y="607"/>
<display autosize="false" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="7" language="java" name="com.iluwatar.cqrs.domain.model.Author" project="cqrs"
file="/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Author.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="608" y="70"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="8" language="java" name="com.iluwatar.cqrs.dto.Author" project="cqrs"
file="/cqrs/src/main/java/com/iluwatar/cqrs/dto/Author.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="834" y="719"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="9" language="java" name="com.iluwatar.cqrs.util.HibernateUtil" project="cqrs"
file="/cqrs/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="662" y="412"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<dependency id="10">
<end type="SOURCE" refId="4"/>
<end type="TARGET" refId="9"/>
</dependency>
<dependency id="11">
<end type="SOURCE" refId="2"/>
<end type="TARGET" refId="9"/>
</dependency>
<realization id="12">
<end type="SOURCE" refId="4"/>
<end type="TARGET" refId="3"/>
</realization>
<association id="13">
<end type="SOURCE" refId="5" navigable="false">
<attribute id="14" name="author"/>
<multiplicity id="15" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="7" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<realization id="16">
<end type="SOURCE" refId="2"/>
<end type="TARGET" refId="1"/>
</realization>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>

124
cqrs/etc/cqrs.urm.puml Normal file
View File

@ -0,0 +1,124 @@
@startuml
package com.iluwatar.cqrs.util {
class HibernateUtil {
- LOGGER : Logger {static}
- SESSIONFACTORY : SessionFactory {static}
+ HibernateUtil()
- buildSessionFactory() : SessionFactory {static}
+ getSessionFactory() : SessionFactory {static}
}
}
package com.iluwatar.cqrs.app {
class App {
+ App()
+ main(args : String[]) {static}
}
}
package com.iluwatar.cqrs.dto {
class Author {
- email : String
- name : String
- username : String
+ Author()
+ Author(name : String, email : String, username : String)
+ equals(obj : Object) : boolean
+ getEmail() : String
+ getName() : String
+ getUsername() : String
+ hashCode() : int
+ toString() : String
}
class Book {
- price : double
- title : String
+ Book()
+ Book(title : String, price : double)
+ equals(obj : Object) : boolean
+ getPrice() : double
+ getTitle() : String
+ hashCode() : int
+ toString() : String
}
}
package com.iluwatar.cqrs.commandes {
class CommandServiceImpl {
- sessionFactory : SessionFactory
+ CommandServiceImpl()
+ authorCreated(username : String, name : String, email : String)
+ authorEmailUpdated(username : String, email : String)
+ authorNameUpdated(username : String, name : String)
+ authorUsernameUpdated(oldUsername : String, newUsername : String)
+ bookAddedToAuthor(title : String, price : double, username : String)
+ bookPriceUpdated(title : String, price : double)
+ bookTitleUpdated(oldTitle : String, newTitle : String)
- getAuthorByUsername(username : String) : Author
- getBookByTitle(title : String) : Book
}
interface ICommandService {
+ authorCreated(String, String, String) {abstract}
+ authorEmailUpdated(String, String) {abstract}
+ authorNameUpdated(String, String) {abstract}
+ authorUsernameUpdated(String, String) {abstract}
+ bookAddedToAuthor(String, double, String) {abstract}
+ bookPriceUpdated(String, double) {abstract}
+ bookTitleUpdated(String, String) {abstract}
}
}
package com.iluwatar.cqrs.queries {
interface IQueryService {
+ getAuthorBooks(String) : List<Book> {abstract}
+ getAuthorBooksCount(String) : BigInteger {abstract}
+ getAuthorByUsername(String) : Author {abstract}
+ getAuthorsCount() : BigInteger {abstract}
+ getBook(String) : Book {abstract}
}
class QueryServiceImpl {
- sessionFactory : SessionFactory
+ QueryServiceImpl()
+ getAuthorBooks(username : String) : List<Book>
+ getAuthorBooksCount(username : String) : BigInteger
+ getAuthorByUsername(username : String) : Author
+ getAuthorsCount() : BigInteger
+ getBook(title : String) : Book
}
}
package com.iluwatar.cqrs.domain.model {
class Author {
- email : String
- id : long
- name : String
- username : String
# Author()
+ Author(username : String, name : String, email : String)
+ getEmail() : String
+ getId() : long
+ getName() : String
+ getUsername() : String
+ setEmail(email : String)
+ setId(id : long)
+ setName(name : String)
+ setUsername(username : String)
+ toString() : String
}
class Book {
- author : Author
- id : long
- price : double
- title : String
# Book()
+ Book(title : String, price : double, author : Author)
+ getAuthor() : Author
+ getId() : long
+ getPrice() : double
+ getTitle() : String
+ setAuthor(author : Author)
+ setId(id : long)
+ setPrice(price : double)
+ setTitle(title : String)
+ toString() : String
}
}
Book --> "-author" Author
CommandServiceImpl ..|> ICommandService
QueryServiceImpl ..|> IQueryService
@enduml

42
cqrs/pom.xml Normal file
View File

@ -0,0 +1,42 @@
<?xml version="1.0"?>
<!-- The MIT License Copyright (c) 2014-2016 Ilkka Seppälä Permission is
hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions: The above copyright notice and this
permission notice shall be included in all copies or substantial portions
of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. -->
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.17.0-SNAPSHOT</version>
</parent>
<artifactId>cqrs</artifactId>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,95 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.cqrs.app;
import java.math.BigInteger;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.iluwatar.cqrs.commandes.CommandServiceImpl;
import com.iluwatar.cqrs.commandes.ICommandService;
import com.iluwatar.cqrs.dto.Author;
import com.iluwatar.cqrs.dto.Book;
import com.iluwatar.cqrs.queries.IQueryService;
import com.iluwatar.cqrs.queries.QueryServiceImpl;
import com.iluwatar.cqrs.util.HibernateUtil;
/**
* CQRS : Command Query Responsibility Segregation. A pattern used to separate query services from commands or writes
* services. The pattern is very simple but it has many consequences. For example, it can be used to tackle down a
* complex domain, or to use other architectures that were hard to implement with the classical way.
*
* This implementation is an example of managing books and authors in a library. The persistence of books and authors is
* done according to the CQRS architecture. A command side that deals with a data model to persist(insert,update,delete)
* objects to a database. And a query side that uses native queries to get data from the database and return objects as
* DTOs (Data transfer Objects).
*
*/
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
/**
* Program entry point
*
* @param args
* command line args
*/
public static void main(String[] args) {
ICommandService commands = new CommandServiceImpl();
// Create Authors and Books using CommandService
commands.authorCreated("eEvans", "Eric Evans", "eEvans@email.com");
commands.authorCreated("jBloch", "Joshua Bloch", "jBloch@email.com");
commands.authorCreated("mFowler", "Martin Fowler", "mFowler@email.com");
commands.bookAddedToAuthor("Domain-Driven Design", 60.08, "eEvans");
commands.bookAddedToAuthor("Effective Java", 40.54, "jBloch");
commands.bookAddedToAuthor("Java Puzzlers", 39.99, "jBloch");
commands.bookAddedToAuthor("Java Concurrency in Practice", 29.40, "jBloch");
commands.bookAddedToAuthor("Patterns of Enterprise Application Architecture", 54.01, "mFowler");
commands.bookAddedToAuthor("Domain Specific Languages", 48.89, "mFowler");
commands.authorNameUpdated("eEvans", "Eric J. Evans");
IQueryService queries = new QueryServiceImpl();
// Query the database using QueryService
Author nullAuthor = queries.getAuthorByUsername("username");
Author eEvans = queries.getAuthorByUsername("eEvans");
BigInteger jBlochBooksCount = queries.getAuthorBooksCount("jBloch");
BigInteger authorsCount = queries.getAuthorsCount();
Book dddBook = queries.getBook("Domain-Driven Design");
List<Book> jBlochBooks = queries.getAuthorBooks("jBloch");
LOGGER.info("Author username : {}", nullAuthor);
LOGGER.info("Author eEvans : {}", eEvans);
LOGGER.info("jBloch number of books : {}", jBlochBooksCount);
LOGGER.info("Number of authors : {}", authorsCount);
LOGGER.info("DDD book : {}", dddBook);
LOGGER.info("jBloch books : {}", jBlochBooks);
HibernateUtil.getSessionFactory().close();
}
}

View File

@ -0,0 +1,123 @@
package com.iluwatar.cqrs.commandes;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import com.iluwatar.cqrs.domain.model.Author;
import com.iluwatar.cqrs.domain.model.Book;
import com.iluwatar.cqrs.util.HibernateUtil;
/**
* This class is an implementation of {@link ICommandService} interface. It uses Hibernate as an api for persistence.
*
*/
public class CommandServiceImpl implements ICommandService {
private SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
private Author getAuthorByUsername(String username) {
Author author = null;
try (Session session = sessionFactory.openSession()) {
Query query = session.createQuery("from Author where username=:username");
query.setParameter("username", username);
author = (Author) query.uniqueResult();
}
if (author == null) {
HibernateUtil.getSessionFactory().close();
throw new NullPointerException("Author " + username + " doesn't exist!");
}
return author;
}
private Book getBookByTitle(String title) {
Book book = null;
try (Session session = sessionFactory.openSession()) {
Query query = session.createQuery("from Book where title=:title");
query.setParameter("title", title);
book = (Book) query.uniqueResult();
}
if (book == null) {
HibernateUtil.getSessionFactory().close();
throw new NullPointerException("Book " + title + " doesn't exist!");
}
return book;
}
@Override
public void authorCreated(String username, String name, String email) {
Author author = new Author(username, name, email);
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.save(author);
session.getTransaction().commit();
}
}
@Override
public void bookAddedToAuthor(String title, double price, String username) {
Author author = getAuthorByUsername(username);
Book book = new Book(title, price, author);
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.save(book);
session.getTransaction().commit();
}
}
@Override
public void authorNameUpdated(String username, String name) {
Author author = getAuthorByUsername(username);
author.setName(name);
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.update(author);
session.getTransaction().commit();
}
}
@Override
public void authorUsernameUpdated(String oldUsername, String newUsername) {
Author author = getAuthorByUsername(oldUsername);
author.setUsername(newUsername);
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.update(author);
session.getTransaction().commit();
}
}
@Override
public void authorEmailUpdated(String username, String email) {
Author author = getAuthorByUsername(username);
author.setEmail(email);
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.update(author);
session.getTransaction().commit();
}
}
@Override
public void bookTitleUpdated(String oldTitle, String newTitle) {
Book book = getBookByTitle(oldTitle);
book.setTitle(newTitle);
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.update(book);
session.getTransaction().commit();
}
}
@Override
public void bookPriceUpdated(String title, double price) {
Book book = getBookByTitle(title);
book.setPrice(price);
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.update(book);
session.getTransaction().commit();
}
}
}

View File

@ -0,0 +1,23 @@
package com.iluwatar.cqrs.commandes;
/**
* This interface represents the commands of the CQRS pattern
*
*/
public interface ICommandService {
void authorCreated(String username, String name, String email);
void bookAddedToAuthor(String title, double price, String username);
void authorNameUpdated(String username, String name);
void authorUsernameUpdated(String oldUsername, String newUsername);
void authorEmailUpdated(String username, String email);
void bookTitleUpdated(String oldTitle, String newTitle);
void bookPriceUpdated(String title, double price);
}

View File

@ -0,0 +1,78 @@
package com.iluwatar.cqrs.domain.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
* This is an Author entity. It is used by Hibernate for persistence.
*
*/
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String username;
private String name;
private String email;
/**
*
* @param username
* username of the author
* @param name
* name of the author
* @param email
* email of the author
*/
public Author(String username, String name, String email) {
super();
this.username = username;
this.name = name;
this.email = email;
}
protected Author() {
super();
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "Author [name=" + name + ", email=" + email + "]";
}
}

View File

@ -0,0 +1,80 @@
package com.iluwatar.cqrs.domain.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
/**
* This is a Book entity. It is used by Hibernate for persistence. Many books can be written by one {@link Author}
*
*/
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String title;
private double price;
@ManyToOne
private Author author;
/**
*
* @param title
* title of the book
* @param price
* price of the book
* @param author
* author of the book
*/
public Book(String title, double price, Author author) {
super();
this.title = title;
this.price = price;
this.author = author;
}
protected Book() {
super();
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
@Override
public String toString() {
return "Book [title=" + title + ", price=" + price + ", author=" + author + "]";
}
}

View File

@ -0,0 +1,71 @@
package com.iluwatar.cqrs.dto;
import java.util.Objects;
/**
*
* This is a DTO (Data Transfer Object) author, contains only useful information to be returned
*
*/
public class Author {
private String name;
private String email;
private String username;
/**
*
* @param name
* name of the author
* @param email
* email of the author
* @param username
* username of the author
*/
public Author(String name, String email, String username) {
super();
this.name = name;
this.email = email;
this.username = username;
}
public Author() {
super();
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public String getUsername() {
return username;
}
@Override
public String toString() {
return "AuthorDTO [name=" + name + ", email=" + email + ", username=" + username + "]";
}
@Override
public int hashCode() {
return Objects.hash(username, name, email);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Author)) {
return false;
}
Author other = (Author) obj;
return username.equals(other.getUsername()) && email.equals(other.getEmail()) && name.equals(other.getName());
}
}

View File

@ -0,0 +1,62 @@
package com.iluwatar.cqrs.dto;
import java.util.Objects;
/**
*
* This is a DTO (Data Transfer Object) book, contains only useful information to be returned
*
*/
public class Book {
private String title;
private double price;
/**
*
* @param title
* title of the book
* @param price
* price of the book
*/
public Book(String title, double price) {
super();
this.title = title;
this.price = price;
}
public Book() {
super();
}
public String getTitle() {
return title;
}
public double getPrice() {
return price;
}
@Override
public String toString() {
return "BookDTO [title=" + title + ", price=" + price + "]";
}
@Override
public int hashCode() {
return Objects.hash(title, price);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Book)) {
return false;
}
Book book = (Book) obj;
return title.equals(book.getTitle()) && price == book.getPrice();
}
}

View File

@ -0,0 +1,26 @@
package com.iluwatar.cqrs.queries;
import java.math.BigInteger;
import java.util.List;
import com.iluwatar.cqrs.dto.Author;
import com.iluwatar.cqrs.dto.Book;
/**
*
* This interface represents the query methods of the CQRS pattern
*
*/
public interface IQueryService {
Author getAuthorByUsername(String username);
Book getBook(String title);
List<Book> getAuthorBooks(String username);
BigInteger getAuthorBooksCount(String username);
BigInteger getAuthorsCount();
}

View File

@ -0,0 +1,83 @@
package com.iluwatar.cqrs.queries;
import java.math.BigInteger;
import java.util.List;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.transform.Transformers;
import com.iluwatar.cqrs.dto.Author;
import com.iluwatar.cqrs.dto.Book;
import com.iluwatar.cqrs.util.HibernateUtil;
/**
* This class is an implementation of {@link IQueryService}. It uses Hibernate native queries to return DTOs from the
* database.
*
*/
public class QueryServiceImpl implements IQueryService {
private SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
@Override
public Author getAuthorByUsername(String username) {
Author authorDTo = null;
try (Session session = sessionFactory.openSession()) {
SQLQuery sqlQuery = session
.createSQLQuery("SELECT a.username as \"username\", a.name as \"name\", a.email as \"email\""
+ "FROM Author a where a.username=:username");
sqlQuery.setParameter("username", username);
authorDTo = (Author) sqlQuery.setResultTransformer(Transformers.aliasToBean(Author.class)).uniqueResult();
}
return authorDTo;
}
@Override
public Book getBook(String title) {
Book bookDTo = null;
try (Session session = sessionFactory.openSession()) {
SQLQuery sqlQuery = session
.createSQLQuery("SELECT b.title as \"title\", b.price as \"price\"" + " FROM Book b where b.title=:title");
sqlQuery.setParameter("title", title);
bookDTo = (Book) sqlQuery.setResultTransformer(Transformers.aliasToBean(Book.class)).uniqueResult();
}
return bookDTo;
}
@Override
public List<Book> getAuthorBooks(String username) {
List<Book> bookDTos = null;
try (Session session = sessionFactory.openSession()) {
SQLQuery sqlQuery = session.createSQLQuery("SELECT b.title as \"title\", b.price as \"price\""
+ " FROM Author a , Book b where b.author_id = a.id and a.username=:username");
sqlQuery.setParameter("username", username);
bookDTos = sqlQuery.setResultTransformer(Transformers.aliasToBean(Book.class)).list();
}
return bookDTos;
}
@Override
public BigInteger getAuthorBooksCount(String username) {
BigInteger bookcount = null;
try (Session session = sessionFactory.openSession()) {
SQLQuery sqlQuery = session.createSQLQuery(
"SELECT count(b.title)" + " FROM Book b, Author a where b.author_id = a.id and a.username=:username");
sqlQuery.setParameter("username", username);
bookcount = (BigInteger) sqlQuery.uniqueResult();
}
return bookcount;
}
@Override
public BigInteger getAuthorsCount() {
BigInteger authorcount = null;
try (Session session = sessionFactory.openSession()) {
SQLQuery sqlQuery = session.createSQLQuery("SELECT count(id) from Author");
authorcount = (BigInteger) sqlQuery.uniqueResult();
}
return authorcount;
}
}

View File

@ -0,0 +1,36 @@
package com.iluwatar.cqrs.util;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class simply returns one instance of {@link SessionFactory} initialized when the application is started
*
*/
public class HibernateUtil {
private static final SessionFactory SESSIONFACTORY = buildSessionFactory();
private static final Logger LOGGER = LoggerFactory.getLogger(HibernateUtil.class);
private static SessionFactory buildSessionFactory() {
// configures settings from hibernate.cfg.xml
final StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
try {
return new MetadataSources(registry).buildMetadata().buildSessionFactory();
} catch (Exception ex) {
StandardServiceRegistryBuilder.destroy(registry);
LOGGER.error("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return SESSIONFACTORY;
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<property name="connection.driver_class">org.h2.Driver</property>
<property name="connection.url">jdbc:h2:mem:test</property>
<property name="connection.username">sa</property>
<property name="hbm2ddl.auto">create</property>
<mapping class="com.iluwatar.cqrs.domain.model.Author" />
<mapping class="com.iluwatar.cqrs.domain.model.Book" />
</session-factory>
</hibernate-configuration>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -0,0 +1,98 @@
package com.iluwatar.cqrs;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.math.BigInteger;
import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import com.iluwatar.cqrs.commandes.CommandServiceImpl;
import com.iluwatar.cqrs.commandes.ICommandService;
import com.iluwatar.cqrs.dto.Author;
import com.iluwatar.cqrs.dto.Book;
import com.iluwatar.cqrs.queries.IQueryService;
import com.iluwatar.cqrs.queries.QueryServiceImpl;
/**
* Integration test of IQueryService and ICommandService with h2 data
*
*/
public class IntegrationTest {
private static IQueryService queryService;
private static ICommandService commandService;
@BeforeClass
public static void initialize() {
commandService = new CommandServiceImpl();
queryService = new QueryServiceImpl();
}
@BeforeClass
public static void populateDatabase() {
// create first author1
commandService.authorCreated("username1", "name1", "email1");
// create author1 and update all its data
commandService.authorCreated("username2", "name2", "email2");
commandService.authorEmailUpdated("username2", "new_email2");
commandService.authorNameUpdated("username2", "new_name2");
commandService.authorUsernameUpdated("username2", "new_username2");
// add book1 to author1
commandService.bookAddedToAuthor("title1", 10, "username1");
// add book2 to author1 and update all its data
commandService.bookAddedToAuthor("title2", 20, "username1");
commandService.bookPriceUpdated("title2", 30);
commandService.bookTitleUpdated("title2", "new_title2");
}
@Test
public void testGetAuthorByUsername() {
Author author = queryService.getAuthorByUsername("username1");
assertEquals("username1", author.getUsername());
assertEquals("name1", author.getName());
assertEquals("email1", author.getEmail());
}
@Test
public void testGetUpdatedAuthorByUsername() {
Author author = queryService.getAuthorByUsername("new_username2");
Author expectedAuthor = new Author("new_name2", "new_email2", "new_username2");
assertEquals(expectedAuthor, author);
}
@Test
public void testGetBook() {
Book book = queryService.getBook("title1");
assertEquals("title1", book.getTitle());
assertEquals(10, book.getPrice(), 0);
}
@Test
public void testGetAuthorBooks() {
List<Book> books = queryService.getAuthorBooks("username1");
assertTrue(books.size() == 2);
assertTrue(books.contains(new Book("title1", 10)));
assertTrue(books.contains(new Book("new_title2", 30)));
}
@Test
public void testGetAuthorBooksCount() {
BigInteger bookCount = queryService.getAuthorBooksCount("username1");
assertEquals(new BigInteger("2"), bookCount);
}
@Test
public void testGetAuthorsCount() {
BigInteger authorCount = queryService.getAuthorsCount();
assertEquals(new BigInteger("2"), authorCount);
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<property name="connection.driver_class">org.h2.Driver</property>
<property name="connection.url">jdbc:h2:mem:test</property>
<property name="connection.username">sa</property>
<property name="hbm2ddl.auto">create</property>
<mapping class="com.iluwatar.cqrs.domain.model.Author" />
<mapping class="com.iluwatar.cqrs.domain.model.Book" />
</session-factory>
</hibernate-configuration>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -142,6 +142,7 @@
<module>balking</module>
<module>extension-objects</module>
<module>marker</module>
<module>cqrs</module>
</modules>