diff --git a/cqrs/README.md b/cqrs/README.md new file mode 100644 index 000000000..3cdd429a1 --- /dev/null +++ b/cqrs/README.md @@ -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) diff --git a/cqrs/etc/cqrs.png b/cqrs/etc/cqrs.png new file mode 100644 index 000000000..28174bc9a Binary files /dev/null and b/cqrs/etc/cqrs.png differ diff --git a/cqrs/etc/cqrs.ucls b/cqrs/etc/cqrs.ucls new file mode 100644 index 000000000..6cdfebbb2 --- /dev/null +++ b/cqrs/etc/cqrs.ucls @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cqrs/etc/cqrs.urm.puml b/cqrs/etc/cqrs.urm.puml new file mode 100644 index 000000000..6dd65dd3a --- /dev/null +++ b/cqrs/etc/cqrs.urm.puml @@ -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 {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 + + 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 \ No newline at end of file diff --git a/cqrs/pom.xml b/cqrs/pom.xml new file mode 100644 index 000000000..6c036a933 --- /dev/null +++ b/cqrs/pom.xml @@ -0,0 +1,42 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.17.0-SNAPSHOT + + cqrs + + + junit + junit + test + + + com.h2database + h2 + + + org.hibernate + hibernate-core + + + diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/app/App.java b/cqrs/src/main/java/com/iluwatar/cqrs/app/App.java new file mode 100644 index 000000000..a943a7f88 --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/app/App.java @@ -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 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(); + } + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java b/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java new file mode 100644 index 000000000..86d9cb10b --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java @@ -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(); + } + } + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/commandes/ICommandService.java b/cqrs/src/main/java/com/iluwatar/cqrs/commandes/ICommandService.java new file mode 100644 index 000000000..eb3cc43a1 --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/commandes/ICommandService.java @@ -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); + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Author.java b/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Author.java new file mode 100644 index 000000000..308fb4f5d --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Author.java @@ -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 + "]"; + } + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Book.java b/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Book.java new file mode 100644 index 000000000..3d14fae47 --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Book.java @@ -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 + "]"; + } + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/dto/Author.java b/cqrs/src/main/java/com/iluwatar/cqrs/dto/Author.java new file mode 100644 index 000000000..b7a7ae880 --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/dto/Author.java @@ -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()); + + } + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/dto/Book.java b/cqrs/src/main/java/com/iluwatar/cqrs/dto/Book.java new file mode 100644 index 000000000..b3f0f62d3 --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/dto/Book.java @@ -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(); + } + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/queries/IQueryService.java b/cqrs/src/main/java/com/iluwatar/cqrs/queries/IQueryService.java new file mode 100644 index 000000000..3e3d6ab10 --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/queries/IQueryService.java @@ -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 getAuthorBooks(String username); + + BigInteger getAuthorBooksCount(String username); + + BigInteger getAuthorsCount(); + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java b/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java new file mode 100644 index 000000000..f3a616d59 --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java @@ -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 getAuthorBooks(String username) { + List 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; + } + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java b/cqrs/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java new file mode 100644 index 000000000..c8f41762e --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java @@ -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; + } + +} diff --git a/cqrs/src/main/resources/hibernate.cfg.xml b/cqrs/src/main/resources/hibernate.cfg.xml new file mode 100644 index 000000000..151983337 --- /dev/null +++ b/cqrs/src/main/resources/hibernate.cfg.xml @@ -0,0 +1,15 @@ + + + + + + org.hibernate.dialect.H2Dialect + org.h2.Driver + jdbc:h2:mem:test + sa + create + + + + \ No newline at end of file diff --git a/cqrs/src/main/resources/logback.xml b/cqrs/src/main/resources/logback.xml new file mode 100644 index 000000000..6b5d24345 --- /dev/null +++ b/cqrs/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/cqrs/src/test/java/com/iluwatar/cqrs/IntegrationTest.java b/cqrs/src/test/java/com/iluwatar/cqrs/IntegrationTest.java new file mode 100644 index 000000000..c9e3b36f4 --- /dev/null +++ b/cqrs/src/test/java/com/iluwatar/cqrs/IntegrationTest.java @@ -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 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); + } + +} diff --git a/cqrs/src/test/resources/hibernate.cfg.xml b/cqrs/src/test/resources/hibernate.cfg.xml new file mode 100644 index 000000000..151983337 --- /dev/null +++ b/cqrs/src/test/resources/hibernate.cfg.xml @@ -0,0 +1,15 @@ + + + + + + org.hibernate.dialect.H2Dialect + org.h2.Driver + jdbc:h2:mem:test + sa + create + + + + \ No newline at end of file diff --git a/cqrs/src/test/resources/logback.xml b/cqrs/src/test/resources/logback.xml new file mode 100644 index 000000000..6b5d24345 --- /dev/null +++ b/cqrs/src/test/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/pom.xml b/pom.xml index 3653a6fe0..4f4b5dee6 100644 --- a/pom.xml +++ b/pom.xml @@ -142,6 +142,7 @@ balking extension-objects marker + cqrs