Vous êtes sur la page 1sur 103

Enterprise Java Training

Brainstorming a
Clean Pragmatic Architecture
Victor Rentea Single Responsibility Principle
7 apr 2017 High Cohesion © Copyright Victor Rentea 2017

A method should do only ONE thing. VictorRentea.ro


A class should have only 1 reason to change..? victor.rentea@gmail.com
@victorrentea
Enterprise Java Training

Brainstorming a
Clean Pragmatic Architecture
Victor Rentea Don’t Repeat Yourself
7 apr 2017 © Copyright Victor Rentea 2017
Never duplicate logic (Capital Sin).
Extract-and-Invoke. VictorRentea.ro
Antonym: Write Everything Twice. victor.rentea@gmail.com
@victorrentea
Enterprise Java Training

Brainstorming a
Clean Pragmatic Architecture
Victor Rentea Keep It Short & Simple
7 apr 2017 Keep It Simple, Stupid - US Navy © Copyright Victor Rentea 2017

Systems work best if kept simple. VictorRentea.ro


Avoid overengineering at all costs. victor.rentea@gmail.com
@victorrentea
Enterprise Java Training

Brainstorming a
Clean Pragmatic Architecture
Victor Rentea
7 apr 2017
Loose Coupling
© Copyright Victor Rentea 2017

VictorRentea.ro
Classes with few dependencies.
victor.rentea@gmail.com
@victorrentea
Enterprise Java Training

Brainstorming a
Clean Pragmatic Architecture
Victor Rentea You Ain’t Gonna Need It
Extreme Programming
7 apr 2017 © Copyright Victor Rentea 2017
Don’t add functionality until deemed necessary.
VictorRentea.ro
Implement things when you actually need them,
NOT when you just foresee that you need them. victor.rentea@gmail.com
@victorrentea
Enterprise Java Training

Brainstorming a
Clean Pragmatic Architecture
Victor Rentea You Ain’t Gonna Need It
Extreme Programming
7 apr 2017 © Copyright Victor Rentea 2017
Don’t add functionality until deemed necessary.
VictorRentea.ro
Implement things when you actually need them,
NOT when you just foresee that you need them. victor.rentea@gmail.com
@victorrentea
Enterprise Java Training

Brainstorming a
Clean Pragmatic Architecture
Victor Rentea Yoda Conditions
7 apr 2017 © Copyright Victor Rentea 2017
if ("a".equals(param)) {
VictorRentea.ro
Avoids NPE. Equals FEAR? victor.rentea@gmail.com
@victorrentea
Enterprise Java Training

Brainstorming a
Clean Pragmatic Architecture
Victor Rentea Favor Composition Over Inheritance
GoF
7 apr 2017 © Copyright Victor Rentea 2017

A extends B is bad. VictorRentea.ro


Use composition: a.setB(b); victor.rentea@gmail.com
@victorrentea
Enterprise Java Training

Brainstorming a
Clean Pragmatic Architecture
Victor Rentea
NOPping
7 apr 2017 © Copyright Victor Rentea 2017

Stanislav sat watching the screensaver VictorRentea.ro


and nopped for a while.
victor.rentea@gmail.com
@victorrentea
Enterprise Java Training

Brainstorming a
Clean Pragmatic Architecture
Victor Rentea Pair Programming
7 apr 2017 © Copyright Victor Rentea 2017
Two programmers working together on one PC.
The driver writes code, the navigator reviews it. VictorRentea.ro
They switch often.
Is cheaper on long-run. victor.rentea@gmail.com
@victorrentea
Enterprise Java Training

Brainstorming a
Clean Pragmatic Architecture
Victor Rentea
Dependency Inversion Principle
7 apr 2017 © Copyright Victor Rentea 2017

Abstractions should not depend on details.


VictorRentea.ro
Details should depend on abstractions.
victor.rentea@gmail.com
@victorrentea
Enterprise Java Training

Brainstorming a
Clean Pragmatic Architecture
Victor Rentea
7 apr 2017 © Copyright Victor Rentea 2017

VictorRentea.ro
victor.rentea@gmail.com
@victorrentea
@

Victor Rentea
Consultant, Technical Lead
Lead Architect for major IBM client
Night Job :
Clean Code Evangelist Trainer & Coach
 Spring and
Speaker
Clean Code, Design Patterns
TDD, Coding Dojos
Java Performance, etc

victorrentea@gmail.com @victorrentea VictorRentea.ro


13 VictorRentea.ro
Agenda
Driving Principles – KISS

Modeling Data – Enemy data

Organizing Logic – Extract when it Grows

Clean Architecture – The Onion

Tests. Fear.

14 VictorRentea.ro
@

Single Responsibility Principle


EmployeeManager
-read/persist
-compute pay-roll vs
-generate PDF report
-manage projects
15 VictorRentea.ro
@

Coupling
vs

Read more: https://dzone.com/articles/probably-the-best-package-structure-in-the-world

16 VictorRentea.ro
@

Don’t Repeat Yourself

17 VictorRentea.ro
@

Keep It Short & Simple


Premature encapsulation is the root of all evil
Overengineering
– Adam Bien

18 VictorRentea.ro
@

Keep It Short & Simple


Premature encapsulation is the root of all evil
Overengineering

Less, simple code Developer Happiness 

19 VictorRentea.ro
@

Keep It Short & Simple


Premature encapsulation is the root of all evil
Overengineering

20 VictorRentea.ro
@

Protect the Developers


Developer
Invisible Magic to reduce effort and risk Comfort

...

Avoid (yet another)


Custom Framework
(to learn)

21 VictorRentea.ro
Request/Thread Scope
@Autowired
private MyRequestContext requestContext;
... {
entity.setModifiedBy(requestContext.getCurrentUser());
}

@Component
@Scope(value = "request", proxyMode = TARGET_CLASS)
class MyRequestContext { ... }

22 VictorRentea.ro
@

Protect the Developers


Developer
Fear Kills Creativity Simplify Unit Testing Safety
- Strong regression defense

23 VictorRentea.ro
@

Always Think
Regular Brainstorming
Regular Refactoring

24 VictorRentea.ro
@

Today, 7 April 2017,


I stopped refactoring.
Today, my application became Legacy
25 VictorRentea.ro
@

Where to implement the domain logic ?

Transaction Script or Domain Driven Design


Procedures working with Anemic Entities Rich Entities (OOP) + many XyzServices
- Map easily to real-world business procedures - Requires deep business involvement
- Promises easier maintenance on the long-run
- Harder to learn
Data

Logic

So I separate logic from data

26 VictorRentea.ro
Agenda
Driving Principles – KISS

Modeling Data – Enemy data

Organizing Logic – Extract when it Grows

Clean Architecture – The Onion

Tests. Fear.

27 VictorRentea.ro
@

Entities hold your persistent data


You control them!
Entity

Logic
Use them to simplify the
implementation of your domain logic
28 VictorRentea.ro
public class Customer { @
// fields, getters and setters, ...
public String getFullName() {
Put small bits of highly return firstName + " " + lastName;
}

reusable public void activate(User user) {


if (status != Status.DRAFT) {
throw new IllegalStateException();
domain logic in your }
status = Status.ACTIVE;
activatedBy = user;
Domain Entities activatedDate = new Date();
}

public boolean isActive() {


return status == Status.ACTIVE;
}

public boolean canPlaceOrders() {


return status == Status.ACTIVE && !isBann
29 } VictorRentea.ro
activatedBy = user; @
activatedDate = new Date();
}
Put small bits of highly
public boolean isActive() {
return status == Status.ACTIVE;
reusable }

public boolean canPlaceOrders() {


domain logic in your return status == Status.ACTIVE && !isBann
}

Fit Domain Entities public void addAddress(Address address) {


address.setCustomer(this);
addresses.add(address);

BlOAt dAnGeR }

public List<Address> getAddresses() {


public String toExportString() { return Collections.unmodifiableList(
return String.format("%s;%s;%d", addresses);
firstName, lastName, isActive()?1:0); }
} }
30 VictorRentea.ro
@
Value Object: grouping of domain data that move together
Small: Developer Comfort
Immutable: Developer Safety
Transient: no persistent ID/PK (vs Entity)
id
public class Money { Entity VO
private final Currency
Currency currency;
currency;
private
private final
float BigDecimal
BigDecimal
amount; amount;
amount;
public Money(Currency currency, BigDecimal amount) {
//this.currency
... = currency;
} this.amount = amount; Logic
validate();
}
public Currency getCurrency() { return currency; }
public BigDecimal getAmount() { return amount; }
public boolean equals(Object other) { ... }
}
31 VictorRentea.ro
@

Then, you start building a User Interface (REST, JSF, ...)

But you quickly realize UI is your enemy.


They want data structures to match the screens.
Their goal is different.
Order.isDeletable:boolean
(to show/hide
the Delete button)

Never expose your Entities to them.


32 VictorRentea.ro
@
public class CustomerDto {
Instead, give your clients private String fullName;
private String phoneNumber;

Data Transfer Objects private Date birthDate;

public final String getFullName


return fullName;
Bare data structures }
public final void setFullName(S
Form/Request DTO }
this.fullName
id
Entity VO = fullName;

public final String getPhoneNum


View/Response return phoneNumber;
} Logic
DTO public final void setPhoneNumbe
{
SearchCriteria/SearchResult this.phoneNumber = phoneNumbe
}
public class CustomerDto {
public final Date getBirthDate(
public String
return fullName;
birthDate;
I personally like them to be dumb public
} String phoneNumber;
public final void setBirthDate(
public Date birthDate;
- public fields ?! !!.. Why not? It’s a struct! }
this.birthDate = birthDate;
}

Why? You’ll see soon… dto.fullName = customer.getFullName();


}
33 VictorRentea.ro
Agenda
Driving Principles – KISS

Modeling Data – Enemy data

Organizing Logic

Clean Architecture – The Onion

Tests. Fear.

34 VictorRentea.ro
@

When DTO  Entity conversion is complex or too long

Extract Mappers to clean up the domain logic

Mapper
DTO
id
Entity VO

API Domain

Logic CustomerDto();
CustomerDto dto = new CustomerDto(customer);
dto.fullName = customer.getFullName();
dto.birthDate = customer.getBirthDate();
dto.phoneNumber = customer.getPhoneNumber();
35 VictorRentea.ro
@

Mappers go to DB ?
KISS: Yes !

36 VictorRentea.ro
@
public Customer toEntityForCreate(CustomerDto dto) {
Actually,
Customer customer = new Customer(); no DB call
customer.setBirthDate(dto.birthDate);
Link to
customer.setGroup(groupRepo.getReference(dto.groupId));...
DB entities
return customer;
}
Mappers
N+1 go to DB ?
Queries
public CustomerDto toDto(Customer customer) {
CustomerDto dto = new CustomerDto(); Lazy-load with ORM

KISS: Yes !
dto.birthDate = customer.getBirthDate();
Load more for (Address a:customer.getAddresses()) {
data from DB for (Address a:addressRepo.getByCustomer(customer.getId())){
...
}
return dto;
Explicit load w/o ORM
} … WHERE ADDR.CUSTOMER_ID = ?
37 } VictorRentea.ro
@

x 1000

N+1 Queries

38 VictorRentea.ro
@
public Customer toEntityForCreate(CustomerDto dto) {
Actually,
Customer customer = new Customer(); no DB call
Link to customer.setBirthDate(dto.birthDate);
DB entities customer.setGroup(groupRepo.getReference(dto.groupId));...
return customer;
}
Mappers go to DB ?
public CustomerDto toDto(Customer customer) {
CustomerDto dto = new CustomerDto(); Lazy-load with ORM

KISS: Yes !
dto.birthDate = customer.getBirthDate();
Load more for (Address a:customer.getAddresses()) {
data from DB for (Address a:addressRepo.getByCustomer(customer.getId())){
...
}
return dto;
Explicit load w/o ORM
} … WHERE ADDR.CUSTOMER_ID = ?
39 } VictorRentea.ro
public List<CustomerDto> search(CustomerCriteria criteria) {
List<Customer> customers = customerRepo.search(criteria);
return customers.stream()
.map(customerMapper::toDto)
.collect(toList());
}

N+1 Queries
(you know the solution)

No Prophecies. public CustomerDto toDto(Customer customer) {


CustomerDto dto = new CustomerDto();
JPQL: LEFT JOIN FETCH customer.addresses
dto.birthDate = customer.getBirthDate();
Simplicity NOW. for (Address a:customer.getAddresses()) {
for (Address a:addressRepo.getByCustomer(customer.getId())){
...
SQL: WHERE ADDR.CUSTOMER_ID IN (?,?,…)
CLEAN CODE  }
return dto;
}

40 VictorRentea.ro
Performance ?

Measure, Don’t Guess®

Premature encapsulation
optimization is the root of all evil
– Donald
– AdamKnuth
Bien

41 VictorRentea.ro
@

public class CustomerDto {


public String fullName;
Mapper
public Date birthDate;
Abused DTOs are reused in different use cases public Long groupId;
- But some fields are not used in some other cases... Which ones ?!..
public UserTime creation;
public UserTime modification;
}
public class CustomerCommonDto {
Strict DTOs don’t have useless fields public String fullName;
- How to avoid duplication ? public Date birthDate;
- Favor Composition over InheritanceGoF (extends is bad) public Long groupId;
}

DRY
public CustomerCommonDto common; public class CustomerView extends CustCommDto{
public UserTime creation;
public UserTime modification;

42 } VictorRentea.ro
@

You are cheating


when’re DRYing code based on a fragile coincidence
(on the client side, the data might be interpreted independently)

43 VictorRentea.ro
@

You are cheating


when’re DRYing code based on a fragile coincidence

Don’t make
a constant here
Extract constants to explain the logic

44 VictorRentea.ro
@

Start implementing domain logic in a Facade

Extract logic into Domain Services Mapper id


Entity VO
- To hide complexity – SRP / KISS
- For Reuse (across Facades or Services) - DRY
Façade Domain
DTO Service

45 VictorRentea.ro
@

Domain Model
Domain Services
speak your Domain Model id
Mapper Entity VO

Fragile, under enemy control

Keep DTOs out of your logic! DTO


Façade Domain
Service
Convert them to your Domain

* That’s why I like dumb DTOs


46 VictorRentea.ro
Aspects
- Transaction
Convert Data Facade Roles
- Logging Mapper
- Global Exception Handling*

id
Entity VO
Façade
DTO Domain
Service

Validator
Validate Data Implement Logic
47 VictorRentea.ro
What do you mean ? How? Huh? If I find a good name,
Piece a cake!
I extract? That’s it?

Extract when it Grows


A Good
Name

He-he!
When a class “There are only two things
grows too big Look for a good class hard in programming:
(>~200 lines?) name to group some Exactly! Cache Invalidation and
 break it of its methods Naming Things”
48 VictorRentea.ro
@

Extract when it Grows


CustomerFacade CustomerPreferecesFacade
saveCustomer() saveCustomerPreferences()
getCustomer() getCustomerPreferences()
searchCustomer() validateAddress()
resetPassword()
saveCustomerPreferences() checkPassworStrength()
getCustomerPreferences()
validateAddress()
resetPassword()
?!!
Same
checkPassworStrength() level of abstraction

49 VictorRentea.ro
@

Separation by Layers of Abstraction

Extract for Reuse


OrderService
AlertService

DeliveryService

In L/XL apps: hard to feel opportunities


 Pair programming, design brainstorming
Increase the Bus Factor

50 VictorRentea.ro
@

Continuous Practice

Pair Programming
VictorRentea.ro
@

Developer Comfort
is essential for
Emerging Architectures

VictorRentea.ro
@

53 VictorRentea.ro
Agenda
Driving Principles – KISS

Modeling Data – Enemy data

Organizing Logic – Extract when it Grows

Clean Architecture – The Onion

Tests. Fear.

54 VictorRentea.ro
What code would you protect?

Domain Objects
id
Entity VO

Domain
Service
Priceless
Domain Logic

Put it in the domain module


55 VictorRentea.ro
Mapper

DTO id
Entity VO
application

Domain
Façade
Service

Validator domain
All the other code
depends on domain
56 VictorRentea.ro
JAXB
JSON
Domain External
domain
DTO
Service Service

57 VictorRentea.ro
JAXB
JSON
Domain External
domain
DTO
Service Service

58 VictorRentea.ro
JAXB
JSON
Domain External
domain
DTO
Service Service

ExtServ
Adapter

59 VictorRentea.ro
External Service Adapter
public class LDAPUserServiceAdapter {
private LdapContext ctx;
...
.
public LdapUserSearchResult findAccountByAccountName(String ldapSearchBase, String accountName) {
try {
String searchFilter = "(&(objectClass=user)(sAMAccountName={1}))";
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);

.
NamingEnumeration<SearchResult> results = ctx.search(
.
ldapSearchBase, searchFilter, new Object[]{accountName} searchControls);

if (!results.hasMoreElements())
.
return null;
}
SearchResult searchResult = (SearchResult) results.nextElement();
if (results.hasMoreElements()) {
throw new MyAppException(ErrorCode.DUPLICATE_USER_IN_LDAP);
}
return new LdapUserSearchResult(searchResult);
} catch (NamingException
. e) {
throw new MyAppException(ErrorCode.LDAP_LOOKUP_FAILED);
}
}
}
60 VictorRentea.ro
JAXB
JSON
Domain External
domain
DTO
DTOs might slip
Service Service
in your domain !

infra
ExtServ
Adapter
Protect your
Domain Model! <dependency>

61 VictorRentea.ro
JAXB
DTOs can’t get in JSON
Domain External
domain
DTO
Service Service

infra
IExtServ implements ExtServ
Adapter Adapter

<dependency>
Protect your
Domain Model!
62 VictorRentea.ro
Dependency Inversion Principle
Higher level modules should not depend on lower-level modules
Speaks your
Domain Model
high-level module low-level module
at runtime, calls
Domain IExtServ implements ExtServ
Service {
class OrderService Adapter
interface IOrderRepo { class Adapter
OrderRepository
@Autowired Order getById(); implements IOrderRepo {
IOrderRepository repo; }
...{ public Order getById() {
repo.getById(id); <dependency> ...
} }
} }

Spring/CDI/Guice/… will inject it Implement it outside


63 VictorRentea.ro
JAXB
JSON
Domain External DTO
Service Service

infra
IExtServ implements ExtServ
Adapter Adapter

<dependency>
Protect your
Domain Model!
domain
64 VictorRentea.ro
JAXB
JSON
External DTO
Service

infra
IExtServ implements ExtServ
Adapter Adapter

<dependency>

domain
65 VictorRentea.ro
JAXB
JSON
External DTO HTTP, RMI, …
Service

JMS

infra
IExtServ implements ExtServ
Adapter Adapter

<dependency> FTP

DB

domain
66 VictorRentea.ro
What code would you protect?

Domain Objects
Interfaces for
id
Entity VO External Services
IExtSrv you consume
Domain Adapter

Service
Priceless Domain Logic IRepo
Interfaces for
Repositories
(DB access)

Put it in the domain module


67 VictorRentea.ro
Behold,
The Onion Architecture
a.k.a. Clean, Hexagonal, Ports-and-Adapters
Mapper

DTO id
Entity VO
IExtSrv
application

infra
Domain Adapter ExtSrv
Façade Adapter
Service
IRepo
F

Repo
Validator implem

68 VictorRentea.ro
Behold,
The Onion Architecture
a.k.a. Clean, Hexagonal, Ports-and-Adapters
Mapper

DTO id
Entity VO
IExtSrv
application

infra
Domain Adapter ExtSrv
Façade Adapter
Service
IRepo
F

Repo
Validator implem

69 VictorRentea.ro
Hide the persistence!
Clean your Logic!

70 VictorRentea.ro
public Employee getById(String employeeId) {
String sql = "SELECT id, name, phone FROM employee WHERE id = ?";
Connection conn = null;
try {
Hide the persistence!
conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
Clean your Logic!
ps.setString(1, employeeId);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
Employee employee = new Employee();
employee.setId(rs.getString(1));
employee.setName(rs.getString(2));
employee.setPhone(rs.getString(3));
return employee;
}
} catch (SQLException e) {
// TODO: something, don't know what..
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// TODO: something, don't know what..
}
}
}
return null;
} Plain Old JDBC (’90-style)
71 VictorRentea.ro
public Employee getById(String employeeId) {
Plain Old JDBC (’90-style)
String sql = "SELECT id, name, phone FROM employee WHERE id = ?";
Connection conn = null;
try {
Hide the persistence!
public Employee getById(String employeeId) {
conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
Clean your Logic!
return jdbcTemplate.queryForObject(
ps.setString(1, employeeId);
"SELECT
ResultSet id, name, phone FROM employee WHERE id = ?",
rs = ps.executeQuery();
if (rs.next()) {
new RowMapper<Employee>() {
Employee employee = new Employee();
public Employee mapRow(ResultSet rs, int rowNum)
employee.setId(rs.getString(1));
throws SQLException {
employee.setName(rs.getString(2));
employee.setPhone(rs.getString(3));
Employee employee = new Employee();
return employee;
} employee.setId(rs.getString(1));
employee.setName(rs.getString(2));
} catch (SQLException e) {
// TODO: something, don't know what..
employee.setPhone(rs.getString(3));
} finally {
if (conn != return
null) { employee;
try { }
conn.close();
},
} catch (SQLException e) {
// employeeId);
TODO: something, don't know what..
} } Spring’ JdbcTemplate (or similar)
}
}
return null;
}
72 VictorRentea.ro
public Employee getById(String employeeId) {
Plain Old JDBC (’90-style)
String sql = "SELECT id, name, phone FROM employee WHERE id = ?";
Connection conn = null;
try {
Hide the persistence!
public Spring’ JdbcTemplate (or similar)
Employee getById(String employeeId) {
conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
Clean your Logic!
return jdbcTemplate.queryForObject(
ps.setString(1, employeeId);
"SELECT
ResultSet id, name, phone FROM employee WHERE id = ?",
rs = ps.executeQuery();
if (rs.next())
public {
interface IOrderRepository
new RowMapper<Employee>() { {
Employee employee = new Employee();
public getEmployeeBasicById(int
Employee Employee mapRow(ResultSetid);
employee.setId(rs.getString(1)); rs, int rowNum)
throws SQLException {
employee.setName(rs.getString(2));
}
employee.setPhone(rs.getString(3));
Employee employee = new Employee();
return employee;
}
<select id="getEmployeeBasicById" parameterType="int"
employee.setId(rs.getString(1)); resultType="Employee">
employee.setName(rs.getString(2));
} catch (SQLException
SELECT e) {name, phone_number AS phoneNumber
id,
// TODO: something, don't know what..
employee.setPhone(rs.getString(3));
} finally { FROM EMPLOYEES
if (conn != return
null) { employee;
try { WHERE
} ID = #{id}
conn.close();
MyBatis DataMapper
}, (SQLException e) {
</select>
} catch
// employeeId);
TODO: something, don't know what..
} }
}
}
return null;
}
73 VictorRentea.ro
public Employee getById(String employeeId) {
Plain Old JDBC (’90-style)
String sql = "SELECT id, name, phone FROM employee WHERE id = ?";
Connection conn = null;
try {
Hide the persistence!
public Spring’ JdbcTemplate (or similar)
Employee getById(String employeeId) {
conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
Clean your Logic!
return jdbcTemplate.queryForObject(
ps.setString(1, employeeId);
"SELECT
ResultSet id, name, phone FROM employee WHERE id = ?",
rs = ps.executeQuery();
class MyBatis DataMapper
if (rs.next()) {
new IOrderRepository
RowMapper<Employee>() {
Employee employee = new Employee();
{
public getEmployeeBasicById(int
Employee Employee mapRow(ResultSetid);
employee.setId(rs.getString(1)); rs, int rowNum)
throwsgetById(String
employee.setName(rs.getString(2));
} public Employee SQLException {id) {
employee.setPhone(rs.getString(3));
Employee employee = list
new Employee();
return employee; List<Employee> = em.createQuery(
<select id="getEmployeeBasicById"
employee.setId(rs.getString(1)); parameterType="int" resultType="Employee">
} "SELECT e from Employee e where e.id=:id", Employee.class)
employee.setName(rs.getString(2));
} catch (SQLException
SELECT e) {name, phone_number AS phoneNumber
id, .setParameter("id", id)
// TODO: something, don't know what..
employee.setPhone(rs.getString(3));
.getResultList();
} finally { FROM EMPLOYEES
if (conn != return { employee;
null) if (list.isEmpty()) {
try { WHERE
} ID = #{id}
conn.close(); return null;
}, (SQLException
</select>
} catch } elsee){{
// employeeId);
TODO: something, don't know what..
return list.get(0);
} }
} }
}
return null;
} JPA/Hibernate
}
74 VictorRentea.ro
public Employee getById(String employeeId) {
Plain Old JDBC (’90-style)
String sql = "SELECT id, name, phone FROM employee WHERE id = ?";
Connection conn = null;
try {
Hide the persistence!
public Spring’ JdbcTemplate (or similar)
Employee getById(String employeeId) {
conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
Clean your Logic!
return jdbcTemplate.queryForObject(
ps.setString(1, employeeId);
"SELECT
ResultSet id, name, phone FROM employee WHERE id = ?",
rs = ps.executeQuery();
class MyBatis DataMapper
if (rs.next()) {
new IOrderRepository
RowMapper<Employee>() {
Employee employee = new Employee();
{
public getEmployeeBasicById(int
Employee Employee mapRow(ResultSetid);
employee.setId(rs.getString(1)); rs, int rowNum)
throwsgetById(String
SQLException {id) {
} JPA/Hibernate
employee.setName(rs.getString(2));
public Employee
employee.setPhone(rs.getString(3));
Employee employee = list
new Employee();
return employee; List<Employee> = em.createQuery(
<select id="getEmployeeBasicById"
employee.setId(rs.getString(1)); parameterType="int" resultType="Employee">
} @Repository
employee.setName(rs.getString(2));
} catch (SQLException
Spring Data JPA - our (fine) selection
"SELECT e from Employee e where e.id=:id", Employee.class)
e) {name, phone_number AS phoneNumber
SELECT id,
public.setParameter("id", id)
interface EmployeeRepository
// TODO: something, don't know what..
employee.setPhone(rs.getString(3));
extends JpaRepository<Employee, Integer>,EmployeeRepositoryCustom
.getResultList(); {
} finally { FROM EMPLOYEES
if (conn != return { employee;
null) if (list.isEmpty()) {
try { WHERE
} ID = #{id}Employee getById(Integer employeeId);
public
conn.close(); return null;
}, (SQLException
</select>
} catch } public
elsee){{Optional<Employee> getByName(String name);
// employeeId); public
TODO: something, Long
don't knowcountByName(String
what.. name);
return list.get(0);
} } }
}
@Query("SELECT e FROM Employee e LEFT JOIN FETCH e.projects")
} }
return null; public List<Employee> getAllFetchProjects();
}
}
75 VictorRentea.ro
Hide the persistence!
Clean your Logic!

76 VictorRentea.ro
Hide the persistence!
Clean your Logic!

77 VictorRentea.ro
Hide the persistence!
DIP Clean your Logic!
implements Repository
IRepository implementation
calls

1 repo/Entity
Order

repo
Repo

Split them
it’s a better, when they grow
Cleaner, Testable
domain
78 world VictorRentea.ro
Mapper

WS DTO
DTO id
Entity VO Interface
IExtSrv
application

infra
Domain Adapter ExtSrv
Façade Adapter
Service
IRepo
F

Repo
Validator implem

79 VictorRentea.ro
Pragmatic
for S/M apps, 2 modules might be enough
Mapper

WS DTO
DTO id
Entity VO Interface
IExtSrv
Domain Adapter ExtSrv
Façade Adapter
Service
IRepo
F
domain
Repo
Validator implem

80 application VictorRentea.ro
That’s all I got on architectures persisting knowledge…

Mapper

WS DTO
DTO id
Entity VO Interface
IExtSrv
application

infra
Domain Adapter ExtSrv
Façade Adapter
IFacade Service
IRepo
F

api Repo
Validator implem

Sent to remote Java clients


81 VictorRentea.ro
Agenda
Driving Principles – KISS

Modeling Data – Enemy data

Organizing Logic – Extract when it Grows

Clean Architecture – The Onion

Tests. Fear.

84 VictorRentea.ro
@

Lots of Unit Tests are Good !

(It’s still a single )


A sense of confidence

Developer Courage

Continuous Refactoring
85 VictorRentea.ro
As you fight to write Unit Tests,
86
the Production code gets simpler VictorRentea.ro
@

Testable code == Good code


Simpler
Decoupled
Live-documented

87 VictorRentea.ro
@

difficulty

Test no.
First couple of unit tests – The most valuable
88 VictorRentea.ro
@

Fast tests, Fast feedback JavaScript


Unit Tests?

A. Input-output
𝑓 𝑥, 𝑦 = 𝑥 2 + 𝑦 2
Pure functions, no side effects. Ideally, no dependencies !

B. Domain logic by mocking dependencies examples


Less readable tests

C. Queries on a fresh database for each test (in-memory)


Reuse complex test data -> Fragile tests ?

89 VictorRentea.ro
@

Short, Readable, Clean tests

@Test(expected = ValidationException.class)
public void withoutNameFails() {
validator.validate(aValidCustomer().withName(null).build());
}

Write Unit Test


is HARD !

Prod:Test code size


1:3 – 1:10
VictorRentea.ro
@

91 VictorRentea.ro
@

Disclaimer Experience in
Mostly Backend Guy Banking, Flavors, ERP and Posting
domains

Projects of size
S-L Open-minded Client
(Freedom to architecture)

92 VictorRentea.ro
Agenda
Driving Principles – KISS

Modeling Data – Enemy data


Organizing Logic – Extract when it Grows
Clean Architecture – The Onion

Tests. Fear.

93 VictorRentea.ro
Takeaways
Agenda
Driving Principles – KISS

Modeling Data – Enemy data


Organizing Logic – Extract when it Grows
Clean Architecture – The Onion

Tests. Fear.

94 VictorRentea.ro
Takeaways
KISS : Avoid overengineering
Magic to protect your Developers
Modeling Data – Enemy data
Organizing Logic – Extract when it Grows
Clean Architecture – The Onion
Tests. Fear.
95 VictorRentea.ro
Takeaways
KISS : Avoid overengineering
Magic to protect your Developers
Enemy data in your DTOs: keep them out
Organizing Logic – Extract when it Grows
Clean Architecture – The Onion
Tests. Fear.
96 VictorRentea.ro
Takeaways
KISS : Avoid overengineering
Magic to protect your Developers
Enemy data in your DTOs: keep them out
Extract when it Grows : for SRP or DRY

Clean Architecture – The Onion


Tests. Fear.
97 VictorRentea.ro
Takeaways
KISS : Avoid overengineering
Magic to protect your Developers
Enemy data in your DTOs: keep them out
Extract when it Grows : for SRP or DRY

The Onion , DIP: domain agnostic to externals


(Adapt® them)
Tests. Fear.
98 VictorRentea.ro
Takeaways
KISS : Avoid overengineering
Magic to protect your Developers
Enemy data in your DTOs: keep them out
Extract when it Grows : for SRP or DRY

The Onion , DIP: domain agnostic to externals


(Adapt® them)
Tests: let them smash your design
99 VictorRentea.ro
KISS
100 VictorRentea.ro
Keep It simple

101 VictorRentea.ro
Where
can I read
Show us
more ?
How to more
apply all code !!
this in my
legacy
code
??

102 ©
Further Reading @

 7 Virtutes of a Good Object - http://commadot.com/wtf-per-minute/


 SOLID is WRONG
 NULL – the worst mistake in IT - https://speakerdeck.com/tastapod/why-every-element-of-solid-is-
- https://dzone.com/articles/the-worst-mistake-of-computer-science- wrong
1
 Good software is written 3 times
 The Clean Architecture: - http://www.javaworld.com/article/2072651/becoming-a-great-
- http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean- programmer--use-your-trash-can.html
architecture.html
 Prezi-like effect in PowerPoint 2016: “Morph”
 Some  Programming Jargon
- http://blog.codinghorror.com/new-programming-jargon/  Extends is bad
 Code quality: WTFs/minute - http://www.yegor256.com/2016/09/13/inheritance-is-
procedural.html
103 VictorRentea.ro
@

HUGE Thanks
to the JPoint Reviewers !!
Vladimir Sitnikov
Anton Arhipov

104 VictorRentea.ro
Enterprise Java Training

Brainstorming a
Clean Pragmatic Architecture
Victor Rentea
7 apr 2017 © Copyright Victor Rentea 2017

VictorRentea.ro
victor.rentea@gmail.com
@victorrentea

Vous aimerez peut-être aussi