Enterprise-less Life
in Jvm World
Yehor Bondar
About Me
- Senior Software Engineer
- Over 5 years in JVM world
- Java/Scala/Groovy developer
- Bunch of JVM frameworks user
Yehor Bondar
key Notes
- What is an enterprise?
- Java lang verbosity pitfalls and ways to resolve it
- JavaEE: Specifications, standards and a lot of implementations
- Frameworks battle to get the currency: JavaEE oriented vs Enteprise-less world
- Conclusions
Enterprise?
Slowness of software is a complex aspect:
- Slower coding
- Slower decision making
- Slower deployments
- Slower support
Ways to exit
- Reduce code verbosity
- Do not over engineering things (KISS)
- Use proper frameworks
- Do we really need monolith?
Is Code verbosity good?
The important point about verbosity is not the time you spend typing: it’s about how difficult it is to read and understand the code.
VS
And verbosity makes code easier to read. You don’t have to know anything to understand the code. Everything you need to know is there in front of you.
java verbosity pitfalls
The verbosity of Java code also encourages developers to implement more abstractions and reusable components. It’s easier to just write more code than to stop and think about reusable solutions. In Java and other verbose languages, ‘coding for the moment’ is a losing strategy.
Well known patterns:
- Abstract Factory & Factory Method;
- Strategy & Facade.
java verbosity pitfalls
- Getters & Setters Hell;
- Checked exceptions;
- Absence of named method parameters and default values;
- No pattern matching
- No closures and lambdas (before Java 8);
- Functional paradigm is weak (type classes, type aliases, variance, etc.)
- Etc.?
GEtters & Setters
Project Lombok
public class Person {
@Getter @Setter private int age = 10;
@Setter(AccessLevel.PROTECTED)
private String name;
@Override public String toString() {
return String.format("%s (age: %d)", name, age);
}
}
case class Person(age: Int, name: String)
class Person(var age: Int, var name: String)
val person = Person(10, "Bob")
person.age = 25
Scala
Checked exceptions
public class Test {
// No throws clause here
public static void main(String[] args) {
doThrow(new SQLException());
}
static void doThrow(Exception e) {
Test.<RuntimeException> doThrow0(e);
}
@SuppressWarnings("unchecked")
static <E extends Exception> void doThrow0(Exception e) throws E {
throw (E) e;
}
}
Checked exceptions
Scala
val result = Try {
throw new SQLException("DB is failed")
}
if(result.isSuccess) ???
else ???
result foreach { db =>
//execute only if DB is OK
}
MEthod parameters
Scala
def test(age: Int, name: String = "exampleText"): Unit = {
}
test(10)
test(10, "realName")
test(age = 10, name = "goodName")
public void test(int age) {
test(age, "exampleText");
}
public void test(int age, String name) {
}
Pattern Matching
trait Person {
def age: Int
def name: String
}
case class Employee(department: String, age: Int, name: String) extends Person
case class Student(university: String, age: Int, name : String) extends Person
val person: Person = Student("KNURE", 30, "Bob")
person match {
case Employee(_, 30, "Bob") => println("Correct man")
case Employee(_, 25, "July") => println("Correct woman")
case Student("KNURE", _, _) => println("Student from KNURE")
case _ => println("somebody else")
}
Lambdas and collections
List<Student> students = persons.stream()
.filter(p -> p.getAge() > 18)
.map(Student::new)
.collect(Collectors.toList());
If you have Java 8
If not
val students = persons.collect {case p if p.age > 18 => new Student(p)}
def students = persons.findAll { person ->
person.age > 18
}.collect { person ->
new Student(person)
}
Groovy Collections API
Solution?
- Avoid Java and use another JVM language
- Include library into your existing project and start refactoring
- Include necessary compiler plugin
- Include library
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.12.1</version>
</dependency>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
TEst Project Overview
- REST-API service
- REST client
- Simple DB iteration
- Unit and Integration tests
Implementations
J2EE
Play 2 Framework
Metrics to measure
- WPP - WTF Per Project
- GPP - Googles Per Project
- Amount of configuration files
- Simplicity
What is not covered
- This is simple project - NOT Production monster
- No perfomance benchmarks - keep it easy
Java Enterprise Edition
XML configurable
EJB (ejb-jar.xml) | JAX-WS (sun-jaxws.xml) |
JPA (persistence.xml) | JAX-RS (web.xml) |
Servlet-Api/JSP (web.xml) | JMS(*-jms.xml) |
JSF (faces-config.xml) | CDI (beans.xml) |
J2EE disadvantages
- XML-hell
- Heavy-weight Application Servers
- Dozen of layers
- Impossible to test outside container
Java EE 7 & Ejb 3.x Improvements
- Annotation based (can be done without ejb-jar.xml)
- DI versus JNDI (@EJB)
- EJB Lite (allows to use EJB features but withing WAR files)
- Increased portability (JNDI names )
- Fully compliment Java EE 7 (optional EJB 2.0)
- JMS 2.0
J2EE in action
- EJB 3.1
- JPA 2.0 / OpenJPA
- Servlet-Api 3.1
- JAX-RS 2.0.1
- TomEE
- Maven
Container?
TESTS?
- Mocks
- Arquillian
- GlassFish Embedded
- Embedded databases: H2, Derby
Results
- Security error - [Ljava.net.URI; is not whitelisted as deserialisable, prevented before loading it.
- openJPA doesn’t support JPA 2.1
- 1 xml file
- WPP: 2
- GPP: 3
WPPs
ResultS
ResultS
public abstract class BaseDao<T extends BaseEntity> {
@PersistenceContext(unitName = "enterpriseless-world")
private EntityManager em;
protected Class<T> entityClass;
public BaseDao() {
ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
this.entityClass =
(Class) genericSuperclass.getActualTypeArguments()[0];
}
@TransactionAttribute
public Long save(T entity) {
em.persist(entity);
return entity.getId();
}
public T find(Long id) {
return em.find(entityClass, id);
}
@TransactionAttribute
public void remove(T entity) {
em.remove(entity);
}
protected EntityManager getEntityManager() {
return em;
}
}
Spring Framework
- Forces to use Spring everywhere
- Very close to be a new Java EE
BUT
Use Spring Boot
If you understand whole Spring Ecosystem
Spring Ecosystem
Alternatives
- Play
- Grails
- Vert.x
- Ratpack
- Spark Framework
- Lagom
- No strict specifications
- MVC only
- Container-less or built-in containers
- Convention over configuration approach
Play 2.x Framework
- Non-blocking I/O (WebSockets included)
- Built on top of Akka & Akka-streams
- Built in testing tools
- Built in ORM but you could choose
- Hot redeploy
- Convention over configuration
- Container-less
- Supports Java and Scala
- Plugins as functionality vendors
Scala in action
- Play Framework:
- Netty (before 2.5) / Akka
- Guice
- SBT
- Built-in Integration tests
- Scalikejdbc
Results
- No xml files were created
- GPP 1 - "How to enable scalikejdbc?"
- WPP 0
Simple controller
class ForeignExchangeController @Inject()(wsClient: WSClient,
foreignExchangeService: ForeignExchangeService) extends Controller {
def postLatestData() = Action.async {
wsClient.url("http://api.fixer.io/latest").get().map { response =>
response.status match {
case play.api.http.Status.OK =>
foreignExchangeService.batchPersist(getData(response.body))
Ok("OK")
case _ => InternalServerError
}
}
}
private def getData(content: String): Seq[(String, Double)] = {
val pattern = "\"(\\S{3}?)\":(\\d+\\.?\\d*)".r
pattern.findAllMatchIn(content).toSeq.collect {
case a if (a.groupCount == 2) => (a.group(1), a.group(2).toDouble)
}
}
}
GET /post controllers.ForeignExchangeController.postLatestData()
Domain iteration
@Singleton
class CurrencyDao @Inject() (val config: Configuration) extends BaseDao {
def batchPersist(data: Seq[(String, Double)]): Unit = DB autoCommit { implicit session =>
val batchParams = data.map {d => Seq(d._1, d._2)}
sql"insert into currencyScala(currency, created, rate) values(?, now(), ?)"
.batch(batchParams: _*).apply()
}
}
case class Currency(id: Option[Long], created: DateTime, currency: String, rate: Double)
Dao vs active record
object DB {
val currencies = TableQuery[CurrencyTable]
}
DB.currencies.filter(_.currency === currency).sortBy(_.created.asc)
val dbConfig = DatabaseConfigProvider.get[JdbcProfile](Play.current)
val currencies = TableQuery[CurrencyTable]
def add(currency: Currency): Future[String] = {
dbConfig.db.run(currencies += user)
}
def delete(id: Long): Future[Int] = {
dbConfig.db.run(currencies.filter(_.id === id).delete)
}
Conclusions
- Use right tool to implement your business requirements
- Do not hesitate to use something new
- Be framework agnostic or use maximum light-weight frameworks
- Keep it simple
Links
Frameworks battle
enterprise-less life
By Yegor Bondar
enterprise-less life
- 1,081