Académique Documents
Professionnel Documents
Culture Documents
Brainstorming a
Clean Pragmatic Architecture
Victor Rentea Single Responsibility Principle
7 apr 2017 High Cohesion © Copyright Victor Rentea 2017
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
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
Brainstorming a
Clean Pragmatic Architecture
Victor Rentea
NOPping
7 apr 2017 © Copyright Victor Rentea 2017
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
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
Tests. Fear.
14 VictorRentea.ro
@
Coupling
vs
16 VictorRentea.ro
@
17 VictorRentea.ro
@
18 VictorRentea.ro
@
19 VictorRentea.ro
@
20 VictorRentea.ro
@
...
@Component
@Scope(value = "request", proxyMode = TARGET_CLASS)
class MyRequestContext { ... }
22 VictorRentea.ro
@
23 VictorRentea.ro
@
Always Think
Regular Brainstorming
Regular Refactoring
24 VictorRentea.ro
@
Logic
26 VictorRentea.ro
Agenda
Driving Principles – KISS
Tests. Fear.
27 VictorRentea.ro
@
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;
}
BlOAt dAnGeR }
Organizing Logic
Tests. Fear.
34 VictorRentea.ro
@
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)
40 VictorRentea.ro
Performance ?
Premature encapsulation
optimization is the root of all evil
– Donald
– AdamKnuth
Bien
41 VictorRentea.ro
@
DRY
public CustomerCommonDto common; public class CustomerView extends CustCommDto{
public UserTime creation;
public UserTime modification;
42 } VictorRentea.ro
@
43 VictorRentea.ro
@
Don’t make
a constant here
Extract constants to explain the logic
44 VictorRentea.ro
@
Fç
45 VictorRentea.ro
@
Domain Model
Domain Services
speak your Domain Model id
Mapper Entity VO
id
Entity VO
Façade
DTO Domain
Service
Fç
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?
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
@
49 VictorRentea.ro
@
DeliveryService
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
Tests. Fear.
54 VictorRentea.ro
What code would you protect?
Domain Objects
id
Entity VO
Domain
Service
Priceless
Domain Logic
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> ...
} }
} }
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)
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
Tests. Fear.
84 VictorRentea.ro
@
Developer Courage
Continuous Refactoring
85 VictorRentea.ro
As you fight to write Unit Tests,
86
the Production code gets simpler VictorRentea.ro
@
87 VictorRentea.ro
@
difficulty
Test no.
First couple of unit tests – The most valuable
88 VictorRentea.ro
@
A. Input-output
𝑓 𝑥, 𝑦 = 𝑥 2 + 𝑦 2
Pure functions, no side effects. Ideally, no dependencies !
89 VictorRentea.ro
@
@Test(expected = ValidationException.class)
public void withoutNameFails() {
validator.validate(aValidCustomer().withName(null).build());
}
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
Tests. Fear.
93 VictorRentea.ro
Takeaways
Agenda
Driving Principles – KISS
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
101 VictorRentea.ro
Where
can I read
Show us
more ?
How to more
apply all code !!
this in my
legacy
code
??
102 ©
Further Reading @
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