SlideShare uma empresa Scribd logo
1 de 39
Dokonalost řešení spočívá ve znalostech
©2015 Ing. Miroslav Svoboda
Vývoj kvalitního sofware
Ú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í.
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í
Design
Ucelená
koncepce
vývoje
Analýza
Kvalita
Integrace
Architektúra
Implementace
Hlavními podklady
pro prezentovaný
návrh koncepce jsou
naše znalosti,
zkušenosti a ověřené
nejmodernejší
postupy.
Výstupem
je ucelená koncepcia
vývoje splňující
současné trendy a
standardy moderních
enterprise aplikací.
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
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
DAO
Bezpečnost
Koncepce
funkčního celku.
Testování
Model
Servisní
vrstva
Datová
vrstva
UI
Konfigurace
KvalitaZáklad pro
moderní a
kvalitní
produkt.
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í)
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
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
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]
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]
DAO vrstva
Minimalizace kódu (Repository)
Specifikace pro filtrování
Stránkování a řazení (Pageable)
Nativní SQL (Query)
Metamodel
Historizace a audit (Envers)
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
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
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
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
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
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)
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
Ří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
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
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
AOP – Spring AOP / AspectJ
<?xml version="1.0" encoding="UTF-8"?>
...
<aop:config>
<aop:aspect ref="methodLogger">
<aop:pointcut id="businessService"
expression="execution(*
com.firma.portal..*ServiceImpl.*(..))" />
<aop:around pointcut-ref="businessService"
method="logMethod" />
</aop:aspect>
</aop:config>
<bean id="methodLogger"
class="com.firma.util.log.MethodLoggerAspect" />
NE
ANO
AOP
nepoužito
(vše ručně v metodách)
log4j.appender.console.layout.ConversionPattern=
%d{dd.MM.yyyy HH:mm:ss} %5p %.3t
[%X{xaIsolation}%X{xaReadOnly} %X{xaName}]
[%X{remoteIpAddress}] %c: %m%n
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)
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
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
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
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 );
}
}
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
Formát a typ zařízení – Content Negotiation
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="contentNegotiationManager" ref="cnManager" />
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">
<property name="prettyPrint" value="true" />
</bean>
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg>
<bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="packagesToScan"><list><value>com.portal.vo</value></list></property></bean>
</constructor-arg>
</bean>
<bean class="org.springframework.web.servlet.view.jasperreports.JasperReportsMultiFormatView">
<property name="url" value="classpath:lv.jrxml" />
</bean>
</list>
</property>
<property name="viewResolvers">
<list>
<bean class="de.bnext.util.spring.web.UrlRedirectViewResolver">
<property name="webflowContext" ref="webflowContext"/></bean>
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.faces.mvc.JsfView" />
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".xhtml" />
</bean>
</list>
</property>
</bean>
http://localhost:8090/cmc-portal/portal/rest/event/1.json
http://localhost:8090/cmc-portal/portal/rest/event/1?format=xml
http://localhost:8090/cmc-portal/portal/rest/event/1/LV_01.pdf
http://localhost:8090/cmc-portal/portal/rest/event/1
Konfigurace a testování
Autentizace (Spring/SSO/RestrictedArea)
Jeden WAR pro všechna prostředí (JNDI, DB props)
Řezy dat
Generování UnitTestů (TestNG)
Kvalita původního projektu – Sonar Qube
Kvalita transformovaného projektu
Ř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
Děláme věci pro jednoduché používání,
ne pouze jednoduché na pohled.
Závěrem
Simple That Works.
Dali jsme přednost „udělat to dobře“
před „udělat to rychle“.
Závěrem
Quality over Quantity.
Odstraňujeme zbytečné a zaměřujeme
se na to, co je opravdu důležité.
Závěrem
Less is More.
Děkujeme
za pozornost.

Mais conteúdo relacionado

Destaque (12)

Center for Student Activities & Leadership Development
Center for Student Activities & Leadership DevelopmentCenter for Student Activities & Leadership Development
Center for Student Activities & Leadership Development
 
Student Engagement
Student EngagementStudent Engagement
Student Engagement
 
ประวัติ rev.1 eng
ประวัติ rev.1 engประวัติ rev.1 eng
ประวัติ rev.1 eng
 
Bibliotecas uniminuto
Bibliotecas uniminutoBibliotecas uniminuto
Bibliotecas uniminuto
 
Cuadernillo de 5 lecturas(2)
Cuadernillo de 5  lecturas(2)Cuadernillo de 5  lecturas(2)
Cuadernillo de 5 lecturas(2)
 
Women's College
Women's CollegeWomen's College
Women's College
 
Тренды сегодня: Big Data
Тренды сегодня: Big DataТренды сегодня: Big Data
Тренды сегодня: Big Data
 
ProfePlus
ProfePlusProfePlus
ProfePlus
 
bkresume
bkresumebkresume
bkresume
 
Inspirações para o poeta
Inspirações para o poetaInspirações para o poeta
Inspirações para o poeta
 
Daniel_Miller
Daniel_MillerDaniel_Miller
Daniel_Miller
 
CVGA Annual General Meeting November 2016
CVGA Annual General Meeting November 2016CVGA Annual General Meeting November 2016
CVGA Annual General Meeting November 2016
 

Semelhante a 201502.ReinIT.Dev

Android nálevna (Czech / Android for beginners)
Android nálevna (Czech / Android for beginners)Android nálevna (Czech / Android for beginners)
Android nálevna (Czech / Android for beginners)
pavelpetrek
 
Testování presenterů v Nette
Testování presenterů v NetteTestování presenterů v Nette
Testování presenterů v Nette
Taste Medio
 

Semelhante a 201502.ReinIT.Dev (20)

Rozšiřujeme jQuery aneb proč si nenapsat plugin?
Rozšiřujeme jQuery aneb proč si nenapsat plugin?Rozšiřujeme jQuery aneb proč si nenapsat plugin?
Rozšiřujeme jQuery aneb proč si nenapsat plugin?
 
JavaScript v GTM - Measure Camp Brno 2017
JavaScript v GTM - Measure Camp Brno 2017JavaScript v GTM - Measure Camp Brno 2017
JavaScript v GTM - Measure Camp Brno 2017
 
React premature performance optimization
React premature performance optimizationReact premature performance optimization
React premature performance optimization
 
Drupal Front-end
Drupal Front-endDrupal Front-end
Drupal Front-end
 
MicroKernel - aneb špatný název pro Helper (5. sraz přátel Symfony v Praze)
MicroKernel - aneb špatný název pro Helper (5. sraz přátel Symfony v Praze)MicroKernel - aneb špatný název pro Helper (5. sraz přátel Symfony v Praze)
MicroKernel - aneb špatný název pro Helper (5. sraz přátel Symfony v Praze)
 
MicroKernel aneb spatny nazev pro Helper (5. sraz pratel Symfony)
MicroKernel aneb spatny nazev pro Helper (5. sraz pratel Symfony)MicroKernel aneb spatny nazev pro Helper (5. sraz pratel Symfony)
MicroKernel aneb spatny nazev pro Helper (5. sraz pratel Symfony)
 
Vývoj aplikací pro iOS
Vývoj aplikací pro iOSVývoj aplikací pro iOS
Vývoj aplikací pro iOS
 
Doctrine - Co dělat když entity nestačí [Filip Procházka] (7. sraz, Praha)
Doctrine - Co dělat když entity nestačí [Filip Procházka] (7. sraz, Praha)Doctrine - Co dělat když entity nestačí [Filip Procházka] (7. sraz, Praha)
Doctrine - Co dělat když entity nestačí [Filip Procházka] (7. sraz, Praha)
 
Android nálevna (Czech / Android for beginners)
Android nálevna (Czech / Android for beginners)Android nálevna (Czech / Android for beginners)
Android nálevna (Czech / Android for beginners)
 
IoC and .NET
IoC and .NETIoC and .NET
IoC and .NET
 
Testování presenterů v Nette
Testování presenterů v NetteTestování presenterů v Nette
Testování presenterů v Nette
 
Linq
LinqLinq
Linq
 
Miroslav Bajtoš - Nativní async/await v Node.js - už tam jsme?
Miroslav Bajtoš - Nativní async/await v Node.js - už tam jsme?Miroslav Bajtoš - Nativní async/await v Node.js - už tam jsme?
Miroslav Bajtoš - Nativní async/await v Node.js - už tam jsme?
 
JavaScript
JavaScriptJavaScript
JavaScript
 
Kdyby/Events #posobota
Kdyby/Events #posobotaKdyby/Events #posobota
Kdyby/Events #posobota
 
Doctrine: co dělat, když entity nestačí
Doctrine: co dělat, když entity nestačíDoctrine: co dělat, když entity nestačí
Doctrine: co dělat, když entity nestačí
 
ClojureScript
ClojureScriptClojureScript
ClojureScript
 
Slovak Sun Training Day 2010 - DTrace
Slovak Sun Training Day 2010 - DTraceSlovak Sun Training Day 2010 - DTrace
Slovak Sun Training Day 2010 - DTrace
 
Doctrine ORM & model
Doctrine ORM & modelDoctrine ORM & model
Doctrine ORM & model
 
Silex
SilexSilex
Silex
 

201502.ReinIT.Dev

  • 1. Dokonalost řešení spočívá ve znalostech ©2015 Ing. Miroslav Svoboda Vývoj kvalitního sofware
  • 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í
  • 4. Design Ucelená koncepce vývoje Analýza Kvalita Integrace Architektúra Implementace Hlavními podklady pro prezentovaný návrh koncepce jsou naše znalosti, zkušenosti a ověřené nejmodernejší postupy. Výstupem je ucelená koncepcia vývoje splňující současné trendy a standardy moderních enterprise aplikací.
  • 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
  • 24. AOP – Spring AOP / AspectJ <?xml version="1.0" encoding="UTF-8"?> ... <aop:config> <aop:aspect ref="methodLogger"> <aop:pointcut id="businessService" expression="execution(* com.firma.portal..*ServiceImpl.*(..))" /> <aop:around pointcut-ref="businessService" method="logMethod" /> </aop:aspect> </aop:config> <bean id="methodLogger" class="com.firma.util.log.MethodLoggerAspect" /> NE ANO AOP nepoužito (vše ručně v metodách) log4j.appender.console.layout.ConversionPattern= %d{dd.MM.yyyy HH:mm:ss} %5p %.3t [%X{xaIsolation}%X{xaReadOnly} %X{xaName}] [%X{remoteIpAddress}] %c: %m%n
  • 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
  • 31. Formát a typ zařízení – Content Negotiation <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="contentNegotiationManager" ref="cnManager" /> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"> <property name="prettyPrint" value="true" /> </bean> <bean class="org.springframework.web.servlet.view.xml.MarshallingView"> <constructor-arg> <bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller"> <property name="packagesToScan"><list><value>com.portal.vo</value></list></property></bean> </constructor-arg> </bean> <bean class="org.springframework.web.servlet.view.jasperreports.JasperReportsMultiFormatView"> <property name="url" value="classpath:lv.jrxml" /> </bean> </list> </property> <property name="viewResolvers"> <list> <bean class="de.bnext.util.spring.web.UrlRedirectViewResolver"> <property name="webflowContext" ref="webflowContext"/></bean> <bean class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="viewClass" value="org.springframework.faces.mvc.JsfView" /> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".xhtml" /> </bean> </list> </property> </bean> http://localhost:8090/cmc-portal/portal/rest/event/1.json http://localhost:8090/cmc-portal/portal/rest/event/1?format=xml http://localhost:8090/cmc-portal/portal/rest/event/1/LV_01.pdf 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.
  • 38. Odstraňujeme zbytečné a zaměřujeme se na to, co je opravdu důležité. Závěrem Less is More.