Compare commits
43 Commits
factoryKit
...
updateThro
Author | SHA1 | Date | |
---|---|---|---|
68a5df1cc9 | |||
1c70cdc333 | |||
55f4ecc234 | |||
3855f0bc15 | |||
d71d2a4abd | |||
67f0a201e2 | |||
3f08aebcd3 | |||
8604e65fce | |||
618b90147a | |||
405495f0a8 | |||
f9ca7f752e | |||
b2a22e506e | |||
1fb1073436 | |||
c28b8edf9e | |||
35ad973730 | |||
a4d77e65f8 | |||
0d1e226258 | |||
dc73d57f3e | |||
4f2becf96f | |||
6fed7cbbb2 | |||
c7c3cb8e19 | |||
6bd6f07bc2 | |||
b0d17a00cd | |||
18b3fa312f | |||
fc75edc7ca | |||
c78d5a3220 | |||
11dcf5a4aa | |||
df84a3cc74 | |||
6523047361 | |||
4a4c136757 | |||
1b5da55d45 | |||
e9b20f674e | |||
23162f474f | |||
1610876fdd | |||
be55b8ebb8 | |||
72aaea5333 | |||
e62a412318 | |||
ff955b4d95 | |||
a7650013a9 | |||
876d2a5466 | |||
636a826bb7 | |||
2230c55582 | |||
f003844c25 |
@ -1776,15 +1776,6 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "castleKing1997",
|
||||
"name": "DragonDreamer",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/35420129?v=4",
|
||||
"profile": "http://rosaecrucis.cn",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
@ -10,7 +10,7 @@
|
||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
<br/>
|
||||
@ -325,7 +325,6 @@ This project is licensed under the terms of the MIT license.
|
||||
<td align="center"><a href="https://github.com/interactwithankush"><img src="https://avatars.githubusercontent.com/u/18613127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>interactwithankush</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=interactwithankush" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/yuhangbin"><img src="https://avatars.githubusercontent.com/u/17566866?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CharlieYu</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=yuhangbin" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Leisterbecker"><img src="https://avatars.githubusercontent.com/u/20650323?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Leisterbecker</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Leisterbecker" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://rosaecrucis.cn"><img src="https://avatars.githubusercontent.com/u/35420129?v=4?s=100" width="100px;" alt=""/><br /><sub><b>DragonDreamer</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=castleKing1997" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
@ -10,115 +10,19 @@ tags:
|
||||
---
|
||||
|
||||
## Intent
|
||||
|
||||
Define a factory of immutable content with separated builder and factory interfaces.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
|
||||
> Imagine a magical weapon factory that can create any type of weapon wished for. When the factory
|
||||
> is unboxed, the master recites the weapon types needed to prepare it. After that, any of those
|
||||
> weapon types can be summoned in an instant.
|
||||
|
||||
In plain words
|
||||
|
||||
> Factory kit is a configurable object builder.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Let's first define the simple `Weapon` hierarchy.
|
||||
|
||||
```java
|
||||
public interface Weapon {
|
||||
}
|
||||
|
||||
public enum WeaponType {
|
||||
SWORD,
|
||||
AXE,
|
||||
BOW,
|
||||
SPEAR
|
||||
}
|
||||
|
||||
public class Sword implements Weapon {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Sword";
|
||||
}
|
||||
}
|
||||
|
||||
// Axe, Bow, and Spear are defined similarly
|
||||
```
|
||||
|
||||
Next, we define a functional interface that allows adding a builder with a name to the factory.
|
||||
|
||||
```java
|
||||
public interface Builder {
|
||||
void add(WeaponType name, Supplier<Weapon> supplier);
|
||||
}
|
||||
```
|
||||
|
||||
The meat of the example is the `WeaponFactory` interface that effectively implements the factory
|
||||
kit pattern. The method `#factory` is used to configure the factory with the classes it needs to
|
||||
be able to construct. The method `#create` is then used to create object instances.
|
||||
|
||||
```java
|
||||
public interface WeaponFactory {
|
||||
|
||||
static WeaponFactory factory(Consumer<Builder> consumer) {
|
||||
var map = new HashMap<WeaponType, Supplier<Weapon>>();
|
||||
consumer.accept(map::put);
|
||||
return name -> map.get(name).get();
|
||||
}
|
||||
|
||||
Weapon create(WeaponType name);
|
||||
}
|
||||
```
|
||||
|
||||
Now, we can show how `WeaponFactory` can be used.
|
||||
|
||||
```java
|
||||
var factory = WeaponFactory.factory(builder -> {
|
||||
builder.add(WeaponType.SWORD, Sword::new);
|
||||
builder.add(WeaponType.AXE, Axe::new);
|
||||
builder.add(WeaponType.SPEAR, Spear::new);
|
||||
builder.add(WeaponType.BOW, Bow::new);
|
||||
});
|
||||
var list = new ArrayList<Weapon>();
|
||||
list.add(factory.create(WeaponType.AXE));
|
||||
list.add(factory.create(WeaponType.SPEAR));
|
||||
list.add(factory.create(WeaponType.SWORD));
|
||||
list.add(factory.create(WeaponType.BOW));
|
||||
list.stream().forEach(weapon -> LOGGER.info("{}", weapon.toString()));
|
||||
```
|
||||
|
||||
Here is the console output when the example is run.
|
||||
|
||||
```
|
||||
21:15:49.709 [main] INFO com.iluwatar.factorykit.App - Axe
|
||||
21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Spear
|
||||
21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Sword
|
||||
21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Bow
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Factory Kit pattern when
|
||||
|
||||
* The factory class can't anticipate the types of objects it must create
|
||||
* A new instance of a custom builder is needed instead of a global one
|
||||
* The types of objects that the factory can build need to be defined outside the class
|
||||
* The builder and creator interfaces need to be separated
|
||||
|
||||
## Related patterns
|
||||
|
||||
* [Builder](https://java-design-patterns.com/patterns/builder/)
|
||||
* [Factory](https://java-design-patterns.com/patterns/factory/)
|
||||
* a class can't anticipate the class of objects it must create
|
||||
* you just want a new instance of a custom builder instead of the global one
|
||||
* you explicitly want to define types of objects, that factory can build
|
||||
* you want a separated builder and creator interface
|
||||
|
||||
## Credits
|
||||
|
||||
* [Design Pattern Reloaded by Remi Forax](https://www.youtube.com/watch?v=-k2X7guaArU)
|
||||
* [Design Pattern Reloaded by Remi Forax: ](https://www.youtube.com/watch?v=-k2X7guaArU)
|
||||
|
@ -23,16 +23,14 @@
|
||||
|
||||
package com.iluwatar.factorykit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Factory kit is a creational pattern that defines a factory of immutable content with separated
|
||||
* Factory-kit is a creational pattern which defines a factory of immutable content with separated
|
||||
* builder and factory interfaces to deal with the problem of creating one of the objects specified
|
||||
* directly in the factory kit instance.
|
||||
* directly in the factory-kit instance.
|
||||
*
|
||||
* <p>In the given example {@link WeaponFactory} represents the factory kit, that contains four
|
||||
* <p>In the given example {@link WeaponFactory} represents the factory-kit, that contains four
|
||||
* {@link Builder}s for creating new objects of the classes implementing {@link Weapon} interface.
|
||||
*
|
||||
* <p>Each of them can be called with {@link WeaponFactory#create(WeaponType)} method, with
|
||||
@ -54,11 +52,7 @@ public class App {
|
||||
builder.add(WeaponType.SPEAR, Spear::new);
|
||||
builder.add(WeaponType.BOW, Bow::new);
|
||||
});
|
||||
var list = new ArrayList<Weapon>();
|
||||
list.add(factory.create(WeaponType.AXE));
|
||||
list.add(factory.create(WeaponType.SPEAR));
|
||||
list.add(factory.create(WeaponType.SWORD));
|
||||
list.add(factory.create(WeaponType.BOW));
|
||||
list.stream().forEach(weapon -> LOGGER.info("{}", weapon.toString()));
|
||||
var axe = factory.create(WeaponType.AXE);
|
||||
LOGGER.info(axe.toString());
|
||||
}
|
||||
}
|
||||
|
@ -1,182 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Metadata Mapping
|
||||
folder: metadata-mapping
|
||||
permalink: /patterns/metadata-mapping/
|
||||
categories: Architectural
|
||||
language: en
|
||||
tags:
|
||||
- Data access
|
||||
---
|
||||
|
||||
## Intent
|
||||
|
||||
Holds details of object-relational mapping in the metadata.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> Hibernate ORM Tool uses Metadata Mapping Pattern to specify the mapping between classes and tables either using XML or annotations in code.
|
||||
|
||||
In plain words
|
||||
|
||||
> Metadata Mapping specifies the mapping between classes and tables so that we could treat a table of any database like a Java class.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> Create a "virtual [object database](https://en.wikipedia.org/wiki/Object_database)" that can be used from within the programming language.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
We give an example about visiting the information of `USER` table in `h2` database. Firstly, we create `USER` table with `h2`:
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
public class DatabaseUtil {
|
||||
private static final String DB_URL = "jdbc:h2:mem:metamapping";
|
||||
private static final String CREATE_SCHEMA_SQL = "DROP TABLE IF EXISTS `user`;"
|
||||
+ "CREATE TABLE `user` (\n"
|
||||
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
|
||||
+ " `username` varchar(255) NOT NULL,\n"
|
||||
+ " `password` varchar(255) NOT NULL,\n"
|
||||
+ " PRIMARY KEY (`id`)\n"
|
||||
+ ");";
|
||||
|
||||
/**
|
||||
* Create database.
|
||||
*/
|
||||
static {
|
||||
LOGGER.info("create h2 database");
|
||||
var source = new JdbcDataSource();
|
||||
source.setURL(DB_URL);
|
||||
try (var statement = source.getConnection().createStatement()) {
|
||||
statement.execute(CREATE_SCHEMA_SQL);
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("unable to create h2 data source", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Correspondingly, here's the basic `User` entity.
|
||||
|
||||
```java
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
public class User {
|
||||
private Integer id;
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* Get a user.
|
||||
* @param username user name
|
||||
* @param password user password
|
||||
*/
|
||||
public User(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then we write a `xml` file to show the mapping between the table and the object:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE hibernate-mapping PUBLIC
|
||||
"-//Hibernate/Hibernate Mapping DTD//EN"
|
||||
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
|
||||
|
||||
<hibernate-mapping>
|
||||
<class name="com.iluwatar.metamapping.model.User" table="user">
|
||||
<id name="id" type="java.lang.Integer" column="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="username" column="username" type="java.lang.String"/>
|
||||
<property name="password" column="password" type="java.lang.String"/>
|
||||
</class>
|
||||
</hibernate-mapping>
|
||||
```
|
||||
|
||||
We use `Hibernate` to resolve the mapping and connect to our database, here's its configuration:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE hibernate-configuration PUBLIC
|
||||
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
|
||||
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
|
||||
<hibernate-configuration>
|
||||
<session-factory>
|
||||
<!-- JDBC Database connection settings -->
|
||||
<property name="connection.url">jdbc:h2:mem:metamapping</property>
|
||||
<property name="connection.driver_class">org.h2.Driver</property>
|
||||
<!-- JDBC connection pool settings ... using built-in test pool -->
|
||||
<property name="connection.pool_size">1</property>
|
||||
<!-- Select our SQL dialect -->
|
||||
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
|
||||
<!-- Echo the SQL to stdout -->
|
||||
<property name="show_sql">false</property>
|
||||
<!-- Drop and re-create the database schema on startup -->
|
||||
<property name="hbm2ddl.auto">create-drop</property>
|
||||
<mapping resource="com/iluwatar/metamapping/model/User.hbm.xml" />
|
||||
</session-factory>
|
||||
</hibernate-configuration>
|
||||
```
|
||||
|
||||
Then we can get access to the table just like an object with `Hibernate`, here's some CRUDs:
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
public class UserService {
|
||||
private static final SessionFactory factory = HibernateUtil.getSessionFactory();
|
||||
|
||||
/**
|
||||
* List all users.
|
||||
* @return list of users
|
||||
*/
|
||||
public List<User> listUser() {
|
||||
LOGGER.info("list all users.");
|
||||
List<User> users = new ArrayList<>();
|
||||
try (var session = factory.openSession()) {
|
||||
var tx = session.beginTransaction();
|
||||
List<User> userIter = session.createQuery("FROM User").list();
|
||||
for (var iterator = userIter.iterator(); iterator.hasNext();) {
|
||||
users.add(iterator.next());
|
||||
}
|
||||
tx.commit();
|
||||
} catch (HibernateException e) {
|
||||
LOGGER.debug("fail to get users", e);
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
// other CRUDs ->
|
||||
...
|
||||
|
||||
public void close() {
|
||||
HibernateUtil.shutdown();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Metadata Mapping when:
|
||||
|
||||
- you want reduce the amount of work needed to handle database mapping.
|
||||
|
||||
## Known uses
|
||||
|
||||
[Hibernate](https://hibernate.org/), [EclipseLink](https://www.eclipse.org/eclipselink/), [MyBatis](https://blog.mybatis.org/)......
|
||||
|
||||
## Credits
|
||||
|
||||
- [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31)
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 49 KiB |
@ -1,32 +0,0 @@
|
||||
@startuml
|
||||
interface com.iluwatar.metamapping.service.UserService {
|
||||
+ List<User> listUser()
|
||||
+ int createUser(User)
|
||||
+ void updateUser(Integer,User)
|
||||
+ void deleteUser(Integer)
|
||||
+ User getUser(Integer)
|
||||
+ void close()
|
||||
}
|
||||
class com.iluwatar.metamapping.utils.DatabaseUtil {
|
||||
+ {static} void createDataSource()
|
||||
}
|
||||
class com.iluwatar.metamapping.model.User {
|
||||
- Integer id
|
||||
- String username
|
||||
- String password
|
||||
+ User(String username, String password)
|
||||
}
|
||||
class com.iluwatar.metamapping.utils.HibernateUtil {
|
||||
+ {static} SessionFactory getSessionFactory()
|
||||
+ {static} void shutdown()
|
||||
}
|
||||
class com.iluwatar.metamapping.App {
|
||||
+ {static} void main(String[])
|
||||
+ {static} List<User> generateSampleUsers()
|
||||
}
|
||||
|
||||
com.iluwatar.metamapping.service.UserService <.. com.iluwatar.metamapping.App
|
||||
com.iluwatar.metamapping.model.User <.. com.iluwatar.metamapping.service.UserService
|
||||
com.iluwatar.metamapping.utils.HibernateUtil <.. com.iluwatar.metamapping.service.UserService
|
||||
com.iluwatar.metamapping.utils.DatabaseUtil <-- com.iluwatar.metamapping.utils.HibernateUtil
|
||||
@enduml
|
@ -1,87 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
The MIT License
|
||||
Copyright © 2014-2021 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 xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>java-design-patterns</artifactId>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<version>1.26.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>metadata-mapping</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.xml.bind</groupId>
|
||||
<artifactId>jaxb-impl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.istack</groupId>
|
||||
<artifactId>istack-commons-runtime</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.iluwatar.metamapping.App</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -1,72 +0,0 @@
|
||||
package com.iluwatar.metamapping;
|
||||
|
||||
import com.iluwatar.metamapping.model.User;
|
||||
import com.iluwatar.metamapping.service.UserService;
|
||||
import com.iluwatar.metamapping.utils.DatabaseUtil;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.hibernate.service.ServiceRegistry;
|
||||
|
||||
/**
|
||||
* Metadata Mapping specifies the mapping
|
||||
* between classes and tables so that
|
||||
* we could treat a table of any database like a Java class.
|
||||
*
|
||||
* <p>With hibernate, we achieve list/create/update/delete/get operations:
|
||||
* 1)Create the H2 Database in {@link DatabaseUtil}.
|
||||
* 2)Hibernate resolve hibernate.cfg.xml and generate service like save/list/get/delete.
|
||||
* For learning metadata mapping pattern, we go deeper into Hibernate here:
|
||||
* a)read properties from hibernate.cfg.xml and mapping from *.hbm.xml
|
||||
* b)create session factory to generate session interacting with database
|
||||
* c)generate session with factory pattern
|
||||
* d)create query object or use basic api with session,
|
||||
* hibernate will convert all query to database query according to metadata
|
||||
* 3)We encapsulate hibernate service in {@link UserService} for our use.
|
||||
* @see org.hibernate.cfg.Configuration#configure(String)
|
||||
* @see org.hibernate.cfg.Configuration#buildSessionFactory(ServiceRegistry)
|
||||
* @see org.hibernate.internal.SessionFactoryImpl#openSession()
|
||||
*/
|
||||
@Slf4j
|
||||
public class App {
|
||||
/**
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args command line args.
|
||||
* @throws Exception if any error occurs.
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
// get service
|
||||
var userService = new UserService();
|
||||
// use create service to add users
|
||||
for (var user: generateSampleUsers()) {
|
||||
var id = userService.createUser(user);
|
||||
LOGGER.info("Add user" + user + "at" + id + ".");
|
||||
}
|
||||
// use list service to get users
|
||||
var users = userService.listUser();
|
||||
LOGGER.info(String.valueOf(users));
|
||||
// use get service to get a user
|
||||
var user = userService.getUser(1);
|
||||
LOGGER.info(String.valueOf(user));
|
||||
// change password of user 1
|
||||
user.setPassword("new123");
|
||||
// use update service to update user 1
|
||||
userService.updateUser(1, user);
|
||||
// use delete service to delete user 2
|
||||
userService.deleteUser(2);
|
||||
// close service
|
||||
userService.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate users.
|
||||
*
|
||||
* @return list of users.
|
||||
*/
|
||||
public static List<User> generateSampleUsers() {
|
||||
final var user1 = new User("ZhangSan", "zhs123");
|
||||
final var user2 = new User("LiSi", "ls123");
|
||||
final var user3 = new User("WangWu", "ww123");
|
||||
return List.of(user1, user2, user3);
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package com.iluwatar.metamapping.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* User Entity.
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
public class User {
|
||||
private Integer id;
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
public User() {}
|
||||
|
||||
/**
|
||||
* Get a user.
|
||||
* @param username user name
|
||||
* @param password user password
|
||||
*/
|
||||
public User(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
package com.iluwatar.metamapping.service;
|
||||
|
||||
import com.iluwatar.metamapping.model.User;
|
||||
import com.iluwatar.metamapping.utils.HibernateUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.SessionFactory;
|
||||
|
||||
/**
|
||||
* Service layer for user.
|
||||
*/
|
||||
@Slf4j
|
||||
public class UserService {
|
||||
private static final SessionFactory factory = HibernateUtil.getSessionFactory();
|
||||
|
||||
/**
|
||||
* List all users.
|
||||
* @return list of users
|
||||
*/
|
||||
public List<User> listUser() {
|
||||
LOGGER.info("list all users.");
|
||||
List<User> users = new ArrayList<>();
|
||||
try (var session = factory.openSession()) {
|
||||
var tx = session.beginTransaction();
|
||||
List<User> userIter = session.createQuery("FROM User").list();
|
||||
for (var iterator = userIter.iterator(); iterator.hasNext();) {
|
||||
users.add(iterator.next());
|
||||
}
|
||||
tx.commit();
|
||||
} catch (HibernateException e) {
|
||||
LOGGER.debug("fail to get users", e);
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a user.
|
||||
* @param user user entity
|
||||
* @return user id
|
||||
*/
|
||||
public int createUser(User user) {
|
||||
LOGGER.info("create user: " + user.getUsername());
|
||||
var id = -1;
|
||||
try (var session = factory.openSession()) {
|
||||
var tx = session.beginTransaction();
|
||||
id = (Integer) session.save(user);
|
||||
tx.commit();
|
||||
} catch (HibernateException e) {
|
||||
LOGGER.debug("fail to create user", e);
|
||||
}
|
||||
LOGGER.info("create user " + user.getUsername() + " at " + id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user.
|
||||
* @param id user id
|
||||
* @param user new user entity
|
||||
*/
|
||||
public void updateUser(Integer id, User user) {
|
||||
LOGGER.info("update user at " + id);
|
||||
try (var session = factory.openSession()) {
|
||||
var tx = session.beginTransaction();
|
||||
user.setId(id);
|
||||
session.update(user);
|
||||
tx.commit();
|
||||
} catch (HibernateException e) {
|
||||
LOGGER.debug("fail to update user", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user.
|
||||
* @param id user id
|
||||
*/
|
||||
public void deleteUser(Integer id) {
|
||||
LOGGER.info("delete user at: " + id);
|
||||
try (var session = factory.openSession()) {
|
||||
var tx = session.beginTransaction();
|
||||
var user = session.get(User.class, id);
|
||||
session.delete(user);
|
||||
tx.commit();
|
||||
} catch (HibernateException e) {
|
||||
LOGGER.debug("fail to delete user", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user.
|
||||
* @param id user id
|
||||
* @return deleted user
|
||||
*/
|
||||
public User getUser(Integer id) {
|
||||
LOGGER.info("get user at: " + id);
|
||||
User user = null;
|
||||
try (var session = factory.openSession()) {
|
||||
var tx = session.beginTransaction();
|
||||
user = session.get(User.class, id);
|
||||
tx.commit();
|
||||
} catch (HibernateException e) {
|
||||
LOGGER.debug("fail to get user", e);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close hibernate.
|
||||
*/
|
||||
public void close() {
|
||||
HibernateUtil.shutdown();
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package com.iluwatar.metamapping.utils;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
|
||||
/**
|
||||
* Create h2 database.
|
||||
*/
|
||||
@Slf4j
|
||||
public class DatabaseUtil {
|
||||
private static final String DB_URL = "jdbc:h2:mem:metamapping";
|
||||
private static final String CREATE_SCHEMA_SQL = "DROP TABLE IF EXISTS `user`;"
|
||||
+ "CREATE TABLE `user` (\n"
|
||||
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
|
||||
+ " `username` varchar(255) NOT NULL,\n"
|
||||
+ " `password` varchar(255) NOT NULL,\n"
|
||||
+ " PRIMARY KEY (`id`)\n"
|
||||
+ ");";
|
||||
|
||||
/**
|
||||
* Hide constructor.
|
||||
*/
|
||||
private DatabaseUtil() {}
|
||||
|
||||
/**
|
||||
* Create database.
|
||||
*/
|
||||
static {
|
||||
LOGGER.info("create h2 database");
|
||||
var source = new JdbcDataSource();
|
||||
source.setURL(DB_URL);
|
||||
try (var statement = source.getConnection().createStatement()) {
|
||||
statement.execute(CREATE_SCHEMA_SQL);
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("unable to create h2 data source", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package com.iluwatar.metamapping.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
|
||||
/**
|
||||
* Manage hibernate.
|
||||
*/
|
||||
@Slf4j
|
||||
public class HibernateUtil {
|
||||
|
||||
private static final SessionFactory sessionFactory = buildSessionFactory();
|
||||
|
||||
/**
|
||||
* Hide constructor.
|
||||
*/
|
||||
private HibernateUtil() {}
|
||||
|
||||
/**
|
||||
* Build session factory.
|
||||
* @return session factory
|
||||
*/
|
||||
private static SessionFactory buildSessionFactory() {
|
||||
// Create the SessionFactory from hibernate.cfg.xml
|
||||
return new Configuration().configure().buildSessionFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session factory.
|
||||
* @return session factory
|
||||
*/
|
||||
public static SessionFactory getSessionFactory() {
|
||||
return sessionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close session factory.
|
||||
*/
|
||||
public static void shutdown() {
|
||||
// Close caches and connection pools
|
||||
getSessionFactory().close();
|
||||
}
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE hibernate-mapping PUBLIC
|
||||
"-//Hibernate/Hibernate Mapping DTD//EN"
|
||||
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
|
||||
|
||||
<hibernate-mapping>
|
||||
<class name="com.iluwatar.metamapping.model.User" table="user">
|
||||
<id name="id" type="java.lang.Integer" column="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="username" column="username" type="java.lang.String"/>
|
||||
<property name="password" column="password" type="java.lang.String"/>
|
||||
</class>
|
||||
</hibernate-mapping>
|
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE hibernate-configuration PUBLIC
|
||||
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
|
||||
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
|
||||
<hibernate-configuration>
|
||||
<session-factory>
|
||||
<!-- JDBC Database connection settings -->
|
||||
<property name="connection.url">jdbc:h2:mem:metamapping</property>
|
||||
<property name="connection.driver_class">org.h2.Driver</property>
|
||||
<!-- JDBC connection pool settings ... using built-in test pool -->
|
||||
<property name="connection.pool_size">1</property>
|
||||
<!-- Select our SQL dialect -->
|
||||
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
|
||||
<!-- Echo the SQL to stdout -->
|
||||
<property name="show_sql">false</property>
|
||||
<!-- Drop and re-create the database schema on startup -->
|
||||
<property name="hbm2ddl.auto">create-drop</property>
|
||||
<mapping resource="com/iluwatar/metamapping/model/User.hbm.xml" />
|
||||
</session-factory>
|
||||
</hibernate-configuration>
|
@ -1,20 +0,0 @@
|
||||
package com.iluwatar.metamapping;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Tests that metadata mapping example runs without errors.
|
||||
*/
|
||||
class AppTest {
|
||||
/**
|
||||
* Issue: Add at least one assertion to this test case.
|
||||
*
|
||||
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
|
||||
* throws an exception.
|
||||
*/
|
||||
@Test
|
||||
void shouldExecuteMetaMappingWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
}
|
||||
}
|
7
pom.xml
7
pom.xml
@ -75,7 +75,6 @@
|
||||
<license-maven-plugin.version>3.0</license-maven-plugin.version>
|
||||
<urm-maven-plugin.version>1.4.8</urm-maven-plugin.version>
|
||||
<commons-io.version>2.7</commons-io.version>
|
||||
<istack-commons-runtime.version>4.0.1</istack-commons-runtime.version>
|
||||
<!-- SonarCloud -->
|
||||
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
|
||||
<sonar.organization>iluwatar</sonar.organization>
|
||||
@ -228,7 +227,6 @@
|
||||
<module>lockable-object</module>
|
||||
<module>fanout-fanin</module>
|
||||
<module>domain-model</module>
|
||||
<module>metadata-mapping</module>
|
||||
</modules>
|
||||
<repositories>
|
||||
<repository>
|
||||
@ -379,11 +377,6 @@
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons-io.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.istack</groupId>
|
||||
<artifactId>istack-commons-runtime</artifactId>
|
||||
<version>${istack-commons-runtime.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
|
@ -18,11 +18,11 @@ threads and eliminating the latency of creating new threads.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
Real-world example
|
||||
|
||||
> We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes
|
||||
> and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we
|
||||
> establish a thread pool.
|
||||
> and serve a mighty amount of coffee cups. Creating a new thread for each task would be a waste so
|
||||
> we establish a thread pool.
|
||||
|
||||
In plain words
|
||||
|
||||
@ -99,7 +99,7 @@ public class PotatoPeelingTask extends Task {
|
||||
}
|
||||
```
|
||||
|
||||
Next we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
|
||||
Next, we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
|
||||
peeling and coffee making.
|
||||
|
||||
```java
|
||||
|
@ -16,10 +16,12 @@ Ensure that a given client is not able to access service resources more than the
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
Real-world example
|
||||
|
||||
> A large multinational corporation offers API to its customers. The API is rate-limited and each
|
||||
> customer can only make certain amount of calls per second.
|
||||
> A young human and an old dwarf walk into a bar. They start ordering beers from the bartender.
|
||||
> The bartender immediately sees that the young human shouldn't consume too many drinks too fast
|
||||
> and refuses to serve if enough time has not passed. For the old dwarf, the serving rate can
|
||||
> be higher.
|
||||
|
||||
In plain words
|
||||
|
||||
@ -33,30 +35,25 @@ In plain words
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Tenant class presents the clients of the API. CallsCount tracks the number of API calls per tenant.
|
||||
`BarCustomer` class presents the clients of the `Bartender` API. `CallsCount` tracks the number of
|
||||
calls per `BarCustomer`.
|
||||
|
||||
```java
|
||||
public class Tenant {
|
||||
public class BarCustomer {
|
||||
|
||||
private final String name;
|
||||
private final int allowedCallsPerSecond;
|
||||
@Getter
|
||||
private final String name;
|
||||
@Getter
|
||||
private final int allowedCallsPerSecond;
|
||||
|
||||
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
if (allowedCallsPerSecond < 0) {
|
||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
if (allowedCallsPerSecond < 0) {
|
||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||
}
|
||||
this.name = name;
|
||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||
callsCount.addTenant(name);
|
||||
}
|
||||
this.name = name;
|
||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||
callsCount.addTenant(name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAllowedCallsPerSecond() {
|
||||
return allowedCallsPerSecond;
|
||||
}
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
@ -76,14 +73,14 @@ public final class CallsCount {
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
LOGGER.debug("Resetting the map.");
|
||||
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
|
||||
LOGGER.info("reset counters");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Next we introduce the service that the tenants are calling. To track the call count we use the
|
||||
throttler timer.
|
||||
Next, the service that the tenants are calling is introduced. To track the call count, a throttler
|
||||
timer is used.
|
||||
|
||||
```java
|
||||
public interface Throttler {
|
||||
@ -111,71 +108,103 @@ public class ThrottleTimerImpl implements Throttler {
|
||||
}, 0, throttlePeriod);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
class B2BService {
|
||||
`Bartender` offers the `orderDrink` service to the `BarCustomer`s. The customers probably don't
|
||||
know that the beer serving rate is limited by their appearances.
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
|
||||
private final CallsCount callsCount;
|
||||
```java
|
||||
class Bartender {
|
||||
|
||||
public B2BService(Throttler timer, CallsCount callsCount) {
|
||||
this.callsCount = callsCount;
|
||||
timer.start();
|
||||
}
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
|
||||
private final CallsCount callsCount;
|
||||
|
||||
public int dummyCustomerApi(Tenant tenant) {
|
||||
var tenantName = tenant.getName();
|
||||
var count = callsCount.getCount(tenantName);
|
||||
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
|
||||
if (count >= tenant.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("API access per second limit reached for: {}", tenantName);
|
||||
return -1;
|
||||
public Bartender(Throttler timer, CallsCount callsCount) {
|
||||
this.callsCount = callsCount;
|
||||
timer.start();
|
||||
}
|
||||
callsCount.incrementCount(tenantName);
|
||||
return getRandomCustomerId();
|
||||
}
|
||||
|
||||
private int getRandomCustomerId() {
|
||||
return ThreadLocalRandom.current().nextInt(1, 10000);
|
||||
}
|
||||
public int orderDrink(BarCustomer barCustomer) {
|
||||
var tenantName = barCustomer.getName();
|
||||
var count = callsCount.getCount(tenantName);
|
||||
if (count >= barCustomer.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName);
|
||||
return -1;
|
||||
}
|
||||
callsCount.incrementCount(tenantName);
|
||||
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count+1);
|
||||
return getRandomCustomerId();
|
||||
}
|
||||
|
||||
private int getRandomCustomerId() {
|
||||
return ThreadLocalRandom.current().nextInt(1, 10000);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per
|
||||
second and Nike to 6.
|
||||
Now it is possible to see the full example in action. `BarCustomer` young human is rate-limited to 2
|
||||
calls per second and the old dwarf to 4.
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
public static void main(String[] args) {
|
||||
var callsCount = new CallsCount();
|
||||
var adidas = new Tenant("Adidas", 5, callsCount);
|
||||
var nike = new Tenant("Nike", 6, callsCount);
|
||||
var human = new BarCustomer("young human", 2, callsCount);
|
||||
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
|
||||
|
||||
var executorService = Executors.newFixedThreadPool(2);
|
||||
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(nike, callsCount));
|
||||
executorService.shutdown();
|
||||
|
||||
try {
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Executor Service terminated: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(10, callsCount);
|
||||
var service = new B2BService(timer, callsCount);
|
||||
executorService.execute(() -> makeServiceCalls(human, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
|
||||
|
||||
executorService.shutdown();
|
||||
try {
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Executor service terminated: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(1000, callsCount);
|
||||
var service = new Bartender(timer, callsCount);
|
||||
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
|
||||
IntStream.range(0, 20).forEach(i -> {
|
||||
service.dummyCustomerApi(tenant);
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||
}
|
||||
IntStream.range(0, 50).forEach(i -> {
|
||||
service.orderDrink(barCustomer);
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
An excerpt from the example's console output:
|
||||
|
||||
```
|
||||
18:46:36.218 [Timer-0] INFO com.iluwatar.throttling.CallsCount - reset counters
|
||||
18:46:36.218 [Timer-1] INFO com.iluwatar.throttling.CallsCount - reset counters
|
||||
18:46:36.242 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [1 consumed]
|
||||
18:46:36.242 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [1 consumed]
|
||||
18:46:36.342 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [2 consumed]
|
||||
18:46:36.342 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [2 consumed]
|
||||
18:46:36.443 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.443 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [3 consumed]
|
||||
18:46:36.544 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.544 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [4 consumed]
|
||||
18:46:36.645 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.645 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.745 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.745 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.846 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.846 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.947 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.947 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:37.048 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:37.048 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:37.148 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:37.148 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||
@ -185,7 +214,7 @@ second and Nike to 6.
|
||||
|
||||
The Throttling pattern should be used:
|
||||
|
||||
* When a service access needs to be restricted to not have high impacts on the performance of the service.
|
||||
* When service access needs to be restricted not to have high impact on the performance of the service.
|
||||
* When multiple clients are consuming the same service resources and restriction has to be made according to the usage per client.
|
||||
|
||||
## Credits
|
||||
|
@ -34,11 +34,11 @@ import lombok.extern.slf4j.Slf4j;
|
||||
* complete service by users or a particular tenant. This can allow systems to continue to function
|
||||
* and meet service level agreements, even when an increase in demand places load on resources.
|
||||
* <p>
|
||||
* In this example we have ({@link App}) as the initiating point of the service. This is a time
|
||||
* In this example there is a {@link Bartender} serving beer to {@link BarCustomer}s. This is a time
|
||||
* based throttling, i.e. only a certain number of calls are allowed per second.
|
||||
* </p>
|
||||
* ({@link Tenant}) is the Tenant POJO class with which many tenants can be created ({@link
|
||||
* B2BService}) is the service which is consumed by the tenants and is throttled.
|
||||
* ({@link BarCustomer}) is the service tenant class having a name and the number of calls allowed.
|
||||
* ({@link Bartender}) is the service which is consumed by the tenants and is throttled.
|
||||
*/
|
||||
@Slf4j
|
||||
public class App {
|
||||
@ -50,33 +50,35 @@ public class App {
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
var callsCount = new CallsCount();
|
||||
var adidas = new Tenant("Adidas", 5, callsCount);
|
||||
var nike = new Tenant("Nike", 6, callsCount);
|
||||
var human = new BarCustomer("young human", 2, callsCount);
|
||||
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
|
||||
|
||||
var executorService = Executors.newFixedThreadPool(2);
|
||||
|
||||
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(nike, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(human, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
|
||||
|
||||
executorService.shutdown();
|
||||
try {
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Executor Service terminated: {}", e.getMessage());
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make calls to the B2BService dummy API.
|
||||
* Make calls to the bartender.
|
||||
*/
|
||||
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(10, callsCount);
|
||||
var service = new B2BService(timer, callsCount);
|
||||
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(1000, callsCount);
|
||||
var service = new Bartender(timer, callsCount);
|
||||
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
|
||||
IntStream.range(0, 20).forEach(i -> {
|
||||
service.dummyCustomerApi(tenant);
|
||||
IntStream.range(0, 50).forEach(i -> {
|
||||
service.orderDrink(barCustomer);
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||
}
|
||||
|
@ -25,22 +25,26 @@ package com.iluwatar.throttling;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
|
||||
/**
|
||||
* A Pojo class to create a basic Tenant with the allowed calls per second.
|
||||
*/
|
||||
public class Tenant {
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* BarCustomer is a tenant with a name and a number of allowed calls per second.
|
||||
*/
|
||||
public class BarCustomer {
|
||||
|
||||
@Getter
|
||||
private final String name;
|
||||
@Getter
|
||||
private final int allowedCallsPerSecond;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param name Name of the tenant
|
||||
* @param allowedCallsPerSecond The number of calls allowed for a particular tenant.
|
||||
* @param name Name of the BarCustomer
|
||||
* @param allowedCallsPerSecond The number of calls allowed for this particular tenant.
|
||||
* @throws InvalidParameterException If number of calls is less than 0, throws exception.
|
||||
*/
|
||||
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
if (allowedCallsPerSecond < 0) {
|
||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||
}
|
||||
@ -48,12 +52,4 @@ public class Tenant {
|
||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||
callsCount.addTenant(name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAllowedCallsPerSecond() {
|
||||
return allowedCallsPerSecond;
|
||||
}
|
||||
}
|
@ -29,33 +29,32 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A service which accepts a tenant and throttles the resource based on the time given to the
|
||||
* tenant.
|
||||
* Bartender is a service which accepts a BarCustomer (tenant) and throttles
|
||||
* the resource based on the time given to the tenant.
|
||||
*/
|
||||
class B2BService {
|
||||
class Bartender {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
|
||||
private final CallsCount callsCount;
|
||||
|
||||
public B2BService(Throttler timer, CallsCount callsCount) {
|
||||
public Bartender(Throttler timer, CallsCount callsCount) {
|
||||
this.callsCount = callsCount;
|
||||
timer.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls dummy customer api.
|
||||
*
|
||||
* Orders a drink from the bartender.
|
||||
* @return customer id which is randomly generated
|
||||
*/
|
||||
public int dummyCustomerApi(Tenant tenant) {
|
||||
var tenantName = tenant.getName();
|
||||
public int orderDrink(BarCustomer barCustomer) {
|
||||
var tenantName = barCustomer.getName();
|
||||
var count = callsCount.getCount(tenantName);
|
||||
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
|
||||
if (count >= tenant.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("API access per second limit reached for: {}", tenantName);
|
||||
if (count >= barCustomer.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName);
|
||||
return -1;
|
||||
}
|
||||
callsCount.incrementCount(tenantName);
|
||||
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count + 1);
|
||||
return getRandomCustomerId();
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ public final class CallsCount {
|
||||
* Resets the count of all the tenants in the map.
|
||||
*/
|
||||
public void reset() {
|
||||
LOGGER.debug("Resetting the map.");
|
||||
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
|
||||
LOGGER.info("reset counters");
|
||||
}
|
||||
}
|
||||
|
@ -23,20 +23,21 @@
|
||||
|
||||
package com.iluwatar.throttling;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
/**
|
||||
* TenantTest to test the creation of Tenant with valid parameters.
|
||||
*/
|
||||
public class TenantTest {
|
||||
public class BarCustomerTest {
|
||||
|
||||
@Test
|
||||
void constructorTest() {
|
||||
assertThrows(InvalidParameterException.class, () -> {
|
||||
new Tenant("FailTenant", -1, new CallsCount());
|
||||
new BarCustomer("sirBrave", -1, new CallsCount());
|
||||
});
|
||||
}
|
||||
}
|
@ -32,19 +32,18 @@ import org.junit.jupiter.api.Test;
|
||||
/**
|
||||
* B2BServiceTest class to test the B2BService
|
||||
*/
|
||||
public class B2BServiceTest {
|
||||
public class BartenderTest {
|
||||
|
||||
private final CallsCount callsCount = new CallsCount();
|
||||
|
||||
@Test
|
||||
void dummyCustomerApiTest() {
|
||||
var tenant = new Tenant("testTenant", 2, callsCount);
|
||||
var tenant = new BarCustomer("pirate", 2, callsCount);
|
||||
// In order to assure that throttling limits will not be reset, we use an empty throttling implementation
|
||||
var timer = (Throttler) () -> {
|
||||
};
|
||||
var service = new B2BService(timer, callsCount);
|
||||
var timer = (Throttler) () -> {};
|
||||
var service = new Bartender(timer, callsCount);
|
||||
|
||||
IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::dummyCustomerApi);
|
||||
IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::orderDrink);
|
||||
var counter = callsCount.getCount(tenant.getName());
|
||||
assertEquals(2, counter, "Counter limit must be reached");
|
||||
}
|
Reference in New Issue
Block a user