2. Únor
26
Prezentace koncepce
Připravili jsme ucelenou více technickou
prezentaci koncepce směru vývoje
kvalitního software podle
nejmodernějších postupů, znalostí a
zkušeností a prezentujeme ji na
životním příběhu dvou odlišných
portálových aplikací.
3. Nejčastější úskalí návrhu systému
53%
31%
16%
Duplicitní kód a kvalita
implementace
Návrh kocepce a
architektury, výkonnost
Problém technologií a
znalosti jejich použítí
5. Náklady na opravu
Analýza
Zjištění
ve fázi
Oprava ve fázi
Architektura
Implementace
Analýza
1x
Architektura
3x
1x
Implementace
5-10x
10x
1x
Testování
10x
15x
10x
Odevzdání
10-100x
25-100x
10-25x
Někdy je lepší přehodnotit stav projektu a zaměřit se na
jeho ekonomickou efektivnost.
Jedním z řešení je efektivní restart.
Náklady na opravu v různých fázích projektu
6. Na co jsme se soustředili
Zachování kompatibility i se stávajícími řešeními
Modulárnost a znovupoužitelnost
Minimalizace programového kódu
Urychlení mapování – nezávislost na řešení
Kvalita, jednoduchost a výkonnost cílové aplikace
Ekonomická návratnost a úspornost
8. Datová vrstva
Převod modelu do gsheet, CSV
Kontrola dat a řezy dat
Identifikace vazeb PK a FK, oprava dat
HSQL v paměti, Oracle – kompatibilita projektem
Centralizace sekvencí (připrava clusterového řešení)
9. Schéma DB – JAXB a JPA
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "table")
public class Table {
@XmlAttribute
private String name;
@XmlAttribute
private String javaName;
...
}
public class Column { ... }
...
[project-schema.xml:
2000 řádek]
<?xml version="1.0"?>
<!DOCTYPE database SYSTEM
"http://db.apache.org/torque/dtd/database_3_3.dtd">
<database name="PORTAL" defaultIdMethod="idbroker"
baseClass="BaseObject" basePeer="BasePeer">
<table name="INSTRUMENT" javaName="Instrument"
idMethod="idbroker">
<column name="INSTRUMENT_ID"
javaName="InstrumentId" required="true"
type="BIGINT" primaryKey="true" />
<column name="TYPE" javaName="Type"
required="false" type="VARCHAR" size="3" />
<column name="ISIN" javaName="Isin„
required="true" type="VARCHAR" size="14" />
...
</database>
NE
ANO
10. Data a řezy dat – Google Sheet / CSV
/** Generovano */
public class UserParser extends
AbstractDataSheetParser<User> {
public UserParser(Map<String, Signification> model){
super(model);
}
@Override
public User parse(Map<String, Object> entry){
User entity = getEntity(User.class,
getLongValue(entry, "USER_ID"), false);
entity.setLasttime(getDateValue(entry,
"LASTTIME"));
}
NE
ANO
Data pouze
v DB
11. Enum mapované na DB – JPA 2.1
@Entity
public class Company implements Serializable {
@Column @Convert(converter =
ECompanyRoleConverter.class)
private ECompanyRole companyRole;
}
@AllArgsConstructor @Getter @ToString
public enum ECompanyRole {
CLIENT("client");
private String code;
}
[ECompanyType.java: 90 řádek]
public enum ECompanyType {
CLIENT( “client" );
private String key;
private static Map<String, EEmployeeType> map;
static {
map = new HashMap<>();
for ( EEmployeeType type :
EEmployeeType.values() ) {
map.put( type.getKey(), type );
}
}
private EEmployeeType( String key ) {
this.key = key;
}
/** @return the (database) key */
public String getKey() {
return this.key;
}
...
}
[ECompanyTypePeer.java: 120 řádek]
[ECompanyTypeDelefate.java: 230 řádek]
NE
ANO [ECompanyRoleConverter.java: 40 řádek]
12. Entity – JPA 2.1
@Entity @Data
@SequenceGenerator(name = "XUSERS_PK", sequenceName =
"XUSERS_PK", allocationSize = 1)
@Table(name = "XUSERS")
@EqualsAndHashCode(of = { "id" }, callSuper = false)
public class User implements Serializable {
@Id @Column(name = "XID")
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "XUSERS_PK")
private Long id;
@Column(name = "XGENDER", length = 1)
private String gender;
... // zadne metody
}
[BaseUsers.java: 2000 řádek]
public abstract class BaseUsers
extends BaseObject {
/** The Peer class */
private static final UsersPeer peer =
new UsersPeer();
/** The value for the userId field */
private long userId;
... // getters and setters
public boolean setByPeerName(String name,
Object value) throws TorqueException,
IllegalArgumentException {
if (UsersPeer.USER_ID.equals(name)) {
return setByName("UserId", value);
}
if (UsersPeer.UNIT_ID.equals(name)) {
return setByName("UnitId", value);
}
...
}
...
}
[Users.java: 20 řádků]
[UsersMapBuilder.java: 415 řádků]
[UsersPeer.java: 1000 řádků]
NE
ANO [User.java: 150 řádek]
13. DAO vrstva
Minimalizace kódu (Repository)
Specifikace pro filtrování
Stránkování a řazení (Pageable)
Nativní SQL (Query)
Metamodel
Historizace a audit (Envers)
14. Entity/VO – Lombok
@SuppressWarnings("serial")
@Data
@EqualsAndHashCode(of = { "id" },
callSuper = false)
@NoArgsConstructor
@AllArgsConstructor
public class CompanyVO implements Serializable {
/** Company ID */
private Long companyId;
}
[CompanyVO.java: 640 řádek]
/** Company ID */
private Long companyId;
/** Default empty constructor */
public CoiCompanyVO() {
}
/** Param constructor */
public CoiCompanyVO(Long companyId) {
this.companyId = companyId;
}
/** @param companyId */
public CoiCompanyVO( long companyId ) {
this.companyId = companyId;
}
/** @param companyId the companyId to set */
public void setCompanyId( long companyId ) {
this.companyId = companyId;
}
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ( int )
( this.companyId ^ ( this.companyId >>> 32 ) );
return result;
}
public boolean equals( Object obj ) { ... }
NE
ANO
15. DAO (Repository) – Spring Data
[UserPeer.java: 980 řádek]
public static Users
retrieveByPK(ObjectKey pk,
Connection con)
throws TorqueException,
NoRowsException, TooManyRowsException {
Criteria criteria = buildCriteria(pk);
List v = doSelect(criteria, con);
if (v.size() == 0) {
throw new
NoRowsException("Failed to select.");
} else if (v.size() > 1) {
throw new
TooManyRowsException("Failed to select.");
} else {
return (Users)v.get(0);
}
}
public static ObjectKey doInsert(
Criteria criteria, Connection con)
throws TorqueException {
correctBooleans(criteria);
setDbName(criteria);
...
}
...
public interface UserRepository extends
CrudRepository<User, Long> {
@Query("from User u where u.username = :username")
User findByUsername(
@Param("username") String username);
}
NE
ANO
16. DAO – filtrování dat - JPA Metadata
@Component("userSpecification")
public class UserSpecificationImpl implements
UserSpecification {
...
@Override
public Predicate toPredicate(Root<User> root,
CriteriaQuery<?> query, CriteriaBuilder cb){
if (StringUtils.isNotBlank(filter.getGender())) {
globalPredicate.add(cb.like(cb.lower(
root.get(User_.gender)),
filter.getGender().toLowerCase()));
}
...
}
[EmitPeer.java: 320 řádek]
public static List<Emittents>
findEmittentsByName(
String searchStr, String countryOfIncorporation,
boolean onlyEmittentNo ) throws Exception {
if ( searchStr == null ) {
searchStr = "";
}
searchStr = onlyEmittentNo ? searchStr.trim()
: "%" + searchStr.trim();
searchStr = searchStr.replaceAll( "['"<>]",
"" ) + "%";
String condition = onlyEmittentNo ? (
" LOWER(EMITTENT_NO) LIKE LOWER('" + searchStr
+ "') " ) : ( " ( LOWER(EMITTENT_NO) LIKE LOWER('„
+ searchStr + "')" + " OR LOWER(EMITTENT_NAME)
LIKE LOWER('" + searchStr + "') ) " );
if (!StringUtils.isBlank(countryOfIncorporation)){
condition += " AND (COUNTRY_CODE = '" +
countryOfIncorporation + "')";
}
condition +=
" AND (DELETED='0' OR DELETED IS NULL) ";
return ( EmittentsPeer.findEmittentsBy(condition));
}
...
[UserSpecificationImpl.java: 150 řádek]
NE
ANO
17. DAO – stránkování dat
public interface UserRepository extends
RevisionRepository<User, Long, Integer>,
JpaSpecificationExecutor<User>,
PagingAndSortingRepository<User, Long> {
@Query("from User u where u.username = :username")
User findByUsername(
@Param("username") String username);
}
Stránkování
neřešeno
NE
ANO
18. Audit DAO – Hibernate Envers
[HUserPeer.java: 860 řádek]
public static ObjectKey doInsert(
Criteria criteria, Connection con)
throws TorqueException {
correctBooleans(criteria);
setDbName(criteria);
...
}
public static void doUpdate(Criteria criteria) throws
TorqueException {
BaseHUsersPeer
.doUpdate(criteria, (Connection) null);
...
}
...
public interface UserRepository extends
CrudRepository<User, Long>,
RevisionRepository<User, Long, Integer> {
@Query("from User u where u.username = :username")
User findByUsername(
@Param("username") String username);
}
@Audited(withModifiedFlag = true)
@AuditTable("H_XUSERS")
@Table(name = "XUSERS")
public class User extends AbstractAuditable<User,
Long> implements Serializable { ... }
NE
ANO
19. Servisní vrstva
Hlavní část implementace (minim. Složitosti)
Zajištěni bezpečnosti (PreAuthorize, UserVO)
Zajištění transakčnosti (Transactional)
Zajištění cache (EHCache, Cacheable)
Číselníky a jejich automatické cachování (Tupelizer)
AOP principy (logování např. se stavem transakce)
20. Bezpečnost – Spring Security
@Service("authenticationService")
public class AuthenticationServiceImpl
implements AuthenticationService {
@PreAuthorize("isAuthenticated()")
public UserVO getCurrentUser() {
Authentication auth =
SecurityContextHolder.getContext()
.getAuthentication();
return (UserVO) auth.getPrincipal();
}
...
}
[NotificationPeer.java]
public static
List<? extends Notifications>
findAllByEmployeeRole( long employeeId,
List<EEmployeeRole> roles, String objectType,
ENotificationType notificationType,
String notificationStatus, Date limitTo )
throws TorqueException {
final List<Notifications> notifications = new
ArrayList<Notifications>();
for ( EEmployeeRole role : roles ) {
if ( EEmployeeRole.LINE_MANAGER.equals( role ) &&
ENotificationType.NOTIFICATION.equals(
notificationType ) &&
PreClearingRequestsPeer.TABLE_NAME.equals(
objectType ) ) {
List<PreClearingRequests> padList =
PreClearingRequestsPeer
.findPreClearingRequestsForEmployees(
EmployeesPeer.findEmployeeListByLM( employeeId
));
// podmínky na role přes všechny vrstvy aplikace
...
return notifications;
}
NE
ANO
21. Řízení transakcí – Spring TX
@Service("authenticationService")
public class AuthenticationServiceImpl
implements AuthenticationService {
@Transactional(readOnly = true)
public UserDetails getUserByUsername(String
username,
Collection<? extends GrantedAuthority>
authorities) {
return converter.map(
userRepository.findByUsername(username),
UserVO.class);
}
...
}
[AccountsPeer.java: 560 řádek]
public static Accounts create(
AccountVO vo, boolean withTransaction,
Connection con ) throws CreateException {
Accounts acc = null;
boolean isWithinTransaction = false;
try {
if ( !withTransaction && ( con == null ||
con.isClosed())) { throw new TorqueException(); }
acc = CommonPeer.createBaseObject(vo,
Accounts.class );
if ( withTransaction ) {
con = Transaction.begin(Torque.getDefaultDB() );
}
...
if ( withTransaction ) {
Transaction.commit( con );
}
} catch( Exception e ) {
if ( withTransaction && isWithinTransaction ) {
Transaction.safeRollback( con );
}
throw new CreateException(e );
}
return ( acc );
}
NE
ANO
22. Cache – Spring Cache / EHCache
@Service("coiEventTypeService")
public class CoiEventTypeServiceImpl implements ... {
@Autowired
private CacheSpecification cacheManager;
@Transactional(readOnly = true)
@Cacheable(value = CODELIST_CACHE,
key = CODELIST_KEY)
public CoiEventTypeVO findCoiEventType( ... );
@Transactional
@CacheEvict(value = CODELIST_CACHE,
key = CODELIST_KEY)
public CoiEventTypeVO saveCoiEventType( ... );
}
NE
ANO
Cache
neřešena
23. Cache pro Lazy – Hibernate Tupelizer
@Entity
@Data
@TuplizerCacheName(cacheName =
CacheSpecification.CODELIST_CACHE)
@Tuplizer(impl = CacheableTuplizer.class)
public class CoiEventType extends
AbstractPersistable<Long> implements Serializable {
...
}
NE
ANO
Lazy Cache
neřešena
25. UI vrstva
Odstínění – pouze VO
Stránkování / filtrování
Verzování resources (šablona, obrázky)
Řízení flow (WebFlow)
REST služby (MVC)
Média a formát (Content Negotiation)
26. UI: odstínění – Orika
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired @Qualifier("converter")
private MapperFacade converter;
@Transactional
public UserVO saveUser(UserVO userVO,
MappingDefinition mappingDefinition) {
User user = userRepository.save(
converter.map(userVO, User.class));
return converter.map(user, UserVO.class);
}
}
Transformace
neřešena
NE
ANO
27. UI: stránkování a filtrování dat
public class CoiEventsPageLazyDataModel extends
AbstractPageLazyDataModel<CoiEventVO, CoiEventFilterVO> {
public Page<CoiEventVO> loadPage(Pageable pageable,
Map<String, Object> filters) {
return coiEventService.findCoiEvents(getFilter(),
pageable, mappingDefinition);
}
public CoiEventVO getRefreshed(String key) {
return coiEventService.findCoiEvent(
NumberUtils.createLong(key), mappingDefinition);
}
public String getKey(CoiEventVO entity) {
return Objects.toString(entity.getEventId());
}
}
Stránkování
neřešeno
NE
ANO
28. UI: mapování potřebných dat
<p:dataTable id="dataTable" var="eventsForUser"
lazy="true" xpaginator="true„ rows="50"
scrollRows="50" liveScroll="true"
scrollHeight="666“ scrollable="true"
value="#{lazyModel.mapping('eventId:eventId,
eventNumber:eventNumber,
type.typeName:type.typeName,
registrationDate:registrationDate,
activeFlag:activeFlag').reset()}"
sortBy="#{eventsForUser.registrationDate}"
selectionMode="single"
selection="#{lazyModel.selected}"
styleClass="event-datatable">
Mapování
neřešeno
NE
ANO
29. UI: řízení flow – Spring Webflow
<var name="lazyModel"
class="com.portal.ui.lazy.CoiEventsPageLazyDataModel" />
<decision-state id="check-input">
<if test="requestParameters.id != null"
then="load-object" else="events" />
</decision-state>
<on-entry>
<evaluate expression="coiEventService.findCoiEvent(
requestParameters.id, mapping.mapping('eventId:eventId,
eventNumber:eventNumber, type.typeName:type.typeName,
activeFlag:activeFlag'))" result="flowScope.event" />
</on-entry>
<view-state id="events">
...
</view-state>
NE
ANO
[EventsBean.java: 2370 řádek]
public void saveClearance() throws
FinderException {
LOG.info( "pressed save clearance" );
if ( isEventGK() ) {
decidedEventIds.add(
this.selectedCheckresults.getEventId() );
this.selectedCheckresults.setGkId(getEmployee ());
}
if ( isTargetEventGK() ) { }
// save
try {
getCoiMgrDelegate().updateCoiCheckresults(
this.selectedCheckresults, decidedEventIds );
initConflictList() ;
} catch( UpdateException e ) {
LOG.error( e, e );
}
try {
PortalUtil.redirectTo(
"/portal/conflict/clearance.xhtml" );
} catch( IOException e ) {
LOG.error( e.getMessage(), e );
}
}
30. REST služby – Spring MVC
@Controller("restController")
@RequestMapping("rest/")
public class RestController {
@Autowired
private CoiEventService coiEventService;
@Autowired
private WebflowContext webflowContext;
@Autowired
private MappingDefinition mapping;
@RequestMapping(value = { "event/{id}" }, method = RequestMethod.GET)
public String getCodeList(Model model, @NotNull @PathVariable("id") Long id) {
if (isRequestForHTML()) {
return "events?id=" + id;
}
CoiEventVO coiEventVO = coiEventService
.findCoiEvent(id, mapping.mapping("eventId:eventId, eventNumber:eventNumber, type.typeId:type.typeId,
type.typeName:type.typeName, registrationDate:registrationDate, activeFlag:activeFlag"));
model.addAttribute("event", coiEventVO);
return "events";
}
}
http://localhost:8090/cmc-portal/portal/rest/event/1
32. Konfigurace a testování
Autentizace (Spring/SSO/RestrictedArea)
Jeden WAR pro všechna prostředí (JNDI, DB props)
Řezy dat
Generování UnitTestů (TestNG)
35. Řešení dlouhodobých plánů
Autentizace SSO
Nahrazení „mrtvých technologií“ (Torque)
Přiblížení se koncepci interního systému (eSuite)
Trasakční management, AOP, řízení výjimek
Zbytečný kód (DAO, UI Bean)
Clusterové/Cloudové řešení i pro další aplikace
36. Děláme věci pro jednoduché používání,
ne pouze jednoduché na pohled.
Závěrem
Simple That Works.
37. Dali jsme přednost „udělat to dobře“
před „udělat to rychle“.
Závěrem
Quality over Quantity.