Die Bordmittel von Java EE ermöglichen Architekturen, von denen man vor wenigen Jahren nur träumen durfte. Nahezu losgelöst von technologischen und schichtenbedingten Restriktionen kommen dank CDI und Co. völlig neue Patterns zum Tragen. Im Gegenzug dazu kann auf „lieb gewonnene“ Workarounds verzichtet werden. Die Session gibt einen Einblick in die vielfältigen Möglichkeiten, die einem der aktuelle Java-Enterprise-Standard bietet, und möchte so zu neuen Denkmustern im Java-EE-Architekturdesign anregen.
3. #WISSENTEILEN
ÜBER MICH
Wer bin ich - und wenn ja, wie viele?
• CIO New Technologies
• Enterprise & Mobile
• Autor, Speaker, Coach & Mentor
• Snowboard & MTB Enthusiast
Lars Röwekamp (a.k.a. @mobileLarson)
LR
9. #WISSENTEILEN
Der Use Case
UC-001: „create Customer“
• Kunde neu erfassen und speichern
• Call Center Agent als Audit-Info speichern
• Begrüßungs-Mail an Neukunden versenden
10. #WISSENTEILEN
Der Use Case
UC-001: „create Customer“
• Kunde neu erfassen und speichern
• Call Center Agent als Audit-Info speichern
• btw: wenn CC Agent Trainee, dann Tracing
• Begrüßungs-Mail an Neukunden versenden
• btw: nur wenn Kunde eMail hinterlegt hat
11. #WISSENTEILEN
Der Use Case
UC-001: „create Customer“
• (JSF) View ruft UI Controller via U-EL
• UI Controller ruft CustomerService EJB/CDI
• UI Controller ruft MailService EJB/CDI
13. #WISSENTEILEN
@Named("createCustomerController")
@RequestScoped
public class CreateCustomerController {
@Inject
private CustomerService customerService;
@Inject
private MailService mailService;
private Customer customer;
public Outcome createCustomer() {
customerService.createCustomer(customer);
mailService.sendMail("Welcome", "Welcome to jeeCRM", customer.getEmail());
return Outcome.SUCCESS;
}
…
}
Und was ist mit dem
Call Center Agent?
14. #WISSENTEILEN
Der Use Case
UC-001: „create Customer“
• (JSF) View ruft UI Controller via U-EL
• UI Controller ruft CustomerService EJB/CDI
• UI Controller ruft MailService EJB/CDI
• UI Controller ruft AuthenticationController
16. #WISSENTEILEN
@Named("createCustomerController")
@RequestScoped
public class CreateCustomerController {
@Inject
private AuthenticationController authController;
...
public Outcome createCustomer() {
CallCenterAgent callCenterAgent = authController.getAuthenticatedUser();
customer.updateAuditInformation(callCenterAgent);
customerService.createCustomer(customer);
mailService.sendMail("Welcome", "Welcome to jeeCRM", customer.getEmail());
return Outcome.SUCCESS;
}
...
}
Ist der Call Center
Agent ein Trainee?
Hat der Kunde
eine eMail?
17. #WISSENTEILEN
@Named("createCustomerController")
@RequestScoped
public class CreateCustomerBean {
@Inject
private CustomerService customerService;
@Inject
private MailService mailService;
@Inject
private TrackService trackService;
@Inject
private AuthenticationController authenController;
private Customer customer;
public Outcome createCustomer() {
// get logged in call center agent for auditing
CrmUser callCenterAgent = authController.getAuthenticatedUser();
// set audit information
currentCustomer.updateAuditInformation(callCenterAgent);
// track call center activity, if trainee
if (callCenterAgent.isTrainee()) {
TrackAction trackAction = TrackAction.ADD_CUSTOMER;
List<TrackParameter> trackParameters = new ArrayList<TrackParameter>();
TrackParameter trackParameter = new TrackParameter(TrackParameterKey.CALL_CENTER_AGENT, callCenterAgent);
trackParameters.add(trackParameter);
trackService.track(trackAction, trackParameters);
}
// add customer to db via customer service
customerService.createCustomer(customer);
// send welcome mail, if customer has an email address
if (customer.getEmail() != null
&& customer.getEmail().length() > 0) {
mailService.sendMail("Welcome", "Welcome to jeeCRM“, customer.getEmail());
}
return Outcome.SUCCESS;
}
}
18. #WISSENTEILEN
@Named("createCustomerController")
@RequestScoped
public class CreateCustomerController {
@Inject
private AuthenticationController authController;
...
public Outcome createCustomer() {
CallCenterAgent callCenterAgent = authController.getAuthenticatedUser();
customer.updateAuditInformation(callCenterAgent);
customerService.createCustomer(customer);
mailService.sendMail("Welcome", "Welcome to jeeCRM", customer.getEmail());
return Outcome.SUCCESS;
}
...
}
Wie steht‘s eigentlich
mit Transaktionen?
19. #WISSENTEILEN
Der Use Case
UC-001: „create Customer“
• (JSF) View ruft UI Controller via U-EL
• UI Controller ruft AuthenticationController
• UI Controller ruft CustomerFacade EJB
• Delegate EJB ruft CustomerService EJB/CDI
• Delegate EJB ruft MailService EJB/CDI
27. #WISSENTEILEN
Der Use Case
UC-002: „delete Customer“
• (JSF) View ruft UI Controller via U-EL
• UI Controller „merkt“ sich Kunde in Session
• UI Controller navigiert zu Bestätigungsseite
• UI Controller bekommt Bestätigung
• …
29. #WISSENTEILEN
@Named("deleteCustomerController")
@SessionScoped
public class DeleteCustomerController implements Serializable {
@Inject
private CustomerService customerService;
private Customer customer;
public Outcome askForDeletion(Customer aCustomer) {
customer = aCustomer;
return Outcome.SUCCESS;
}
public Outcome deleteCustomer() {
customerService.deleteCustomer(customer);
return Outcome.SUCCESS;
}
}
Wie kommt der Kunde
aus der Session raus?
33. #WISSENTEILEN
Problemkind Schichtenmodell
• UI Controller via CDI Managed Beans
• Service Facade und Services via EJB/CDI
• Persistenz via EntityManager und Entities
• Alles nur Infrastruktur!
• Wo steckt eigentlich die fachliche Domain?
53. #WISSENTEILEN
// Customer Service EJB
@Stateless
public class CustomerService {
@Inject @Current
private CallCenterAgent currentCallCenterAgent;
...
@PersistenceContext
private EntityManager em;
// transactional by default
public void createCustomer(Customer customer) {
customer.updateAuditInformation(currentCallCenterAgent);
em.persist(customer);
}
...
}
Warum EJB?
54. #WISSENTEILEN
// Customer Service CDI ManagedBean
@ApplicationScoped
public class CustomerService {
@Inject @Current
private CallCenterAgent currentCallCenterAgent;
...
@PersistenceContext
private EntityManager em;
// transactional by default
public void createCustomer(Customer customer) {
customer.updateAuditInformation(currentCallCenterAgent);
em.persist(customer);
}
...
} Ok, aber wo bekommen wir
jetzt die Transaktion her?
55. #WISSENTEILEN
// Customer Service CDI ManagedBean
@ApplicationScoped
public class CustomerService {
@Inject @Current
private CallCenterAgent currentCallCenterAgent;
...
@PersistenceContext
private EntityManager em;
@javax.transaction.Transactional // JTA 1.2 – TxType.REQUIRED by default
public void createCustomer(Customer customer) {
customer.updateAuditInformation(currentCallCenterAgent);
em.persist(customer);
}
...
}
62. #WISSENTEILEN
Fachlicher Scope
Anti-Pattern „pumped up Session“
• RequestScoped ist zu kurz
• ViewScoped passt irgendwie auch nicht
• SessionScoped aus Mangel an Alternativen
• FlowScoped (ab Java EE 7) als Lösung?
63. #WISSENTEILEN
@Named("deleteCustomerController")
@SessionScoped
public class DeleteCustomerController implements Serializable {
@Inject
private CustomerService customerService;
private Customer customer;
public Outcome askForDeletion(Customer aCustomer) {
customer = aCustomer;
return Outcome.SUCCESS;
}
public Outcome deleteCustomer() {
customerService.deleteCustomer(customer);
return Outcome.SUCCESS;
}
}
67. #WISSENTEILEN
@Named("deleteCustomerController")
@ConversationScoped
public class DeleteCustomerController implements Serializable {
@Inject
private Conversation customerDeleteConversation;
...
public Outcome askForDeletion(Customer aCustomer) {
customerDeleteConversation.begin();
customerToDelete = aCustomer;
return Outcome.SUCCESS;
}
@Transactional
public Outcome deleteCustomer() {
... // call backend services
customerDeleteConversation.end();
return Outcome.SUCCESS;
}
}
Was ist, wenn
Conversation schon
aktiv?
Was ist, wenn
Conversation noch
aktiv?
72. #WISSENTEILEN
Fachliche Methoden
a.k.a. „no more doAll(…)-Methods“
• Primärer Use Case: create Customer
• Sekundärer Use Case: send Welcome Mail
• Sekundärer Use Case: trace Trainee Audit Info
• Sekundärer Use Case: if tenant X …
79. #WISSENTEILEN
// event to indicate that a new customer was created
public class CustomerCreatedEvent {
private Customer customer;
public CustomerCreatedEvent(Customer customer) {
this.customer = customer;
}
public Customer getCustomer() {
return this.customer;
}
}
80. #WISSENTEILEN
@Named("createCustomerController")
@RequestScoped
public class CreateCustomerController {
@Inject
private Event<CustomerCreatedEvent> customerCreatedEventSource;
@Inject
private CustomerService customerService;
private Customer currentCustomer;
@Transactional
public Outcome addCustomer() {
// add customer to db via customer service
customerService.createCustomer(currentCustomer);
customerCreatedEventSource.fire(new CustomerCreatedEvent(currentCustomer));
return Outcome.SUCCESS;
} …
}
81. #WISSENTEILEN
@Named("createCustomerController")
@RequestScoped
public class CreateCustomerController {
@Inject
private Event<CustomerCreatedEvent> customerCreatedEventSource;
@Inject
private CustomerService customerService;
private Customer currentCustomer;
@Transactional
public Outcome addCustomer() {
// add customer to db via customer service
customerService.createCustomer(currentCustomer);
customerCreatedEventSource.fire(new CustomerCreatedEvent(currentCustomer));
return Outcome.SUCCESS;
} …
}
82. #WISSENTEILEN
@ApplicationScoped
public class Mailer implements Serializable {
@Inject
private MailService mailService;
public void sendWelcomeMail(@Observes CustomerCreatedEvent event) {
// send welcome mail, if customer has an email address
Customer customer = event.getCustomer();
if (customer.getEmail() != null) {
mailService.sendMail(...);
}
}
public void sendGoodbyeMail(@Observes CustomerDeletedEvent event) {
...
}
}
83. #WISSENTEILEN
@Named("createCustomerController")
@RequestScoped
public class CreateCustomerController {
@Inject
private Event<CustomerCreatedEvent> eventSource;
@Inject
private CustomerService customerService;
private Customer currentCustomer;
@Transactional
public Outcome addCustomer() {
// add customer to db via customer service
customerService.createCustomer(currentCustomer);
eventSource.fire(new CustomerCreatedEvent(currentCustomer));
return Outcome.SUCCESS;
} …
}
Infrastruktur vs
Fachlichkeit?
84. #WISSENTEILEN
@Named("createCustomerController")
@RequestScoped
public class CreateCustomerController {
@Inject @Created
private Event<Customer> eventSource;
@Inject
private CustomerService customerService;
private Customer currentCustomer;
@Transactional
public Outcome addCustomer() {
// add customer to db via customer service
customerService.createCustomer(currentCustomer);
eventSource.fire(currentCustomer));
return Outcome.SUCCESS;
} …
}
85. #WISSENTEILEN
@ApplicationScoped
public class Mailer implements Serializable {
@Inject
MailService mailService;
public void sendWelcomeMail(@Observes @Created Customer customer) {
// send welcome mail, if customer has an email address
if (customer.getEmail() != null) {
mailService.sendMail(...);
}
}
public void sendGoodbyeMail(@Observes @Deleted Customer customer) {
...
}
}
86. #WISSENTEILEN
@ApplicationScoped
public class TrackingService implements Serializable {
@Inject @Current
private CallCenterAgent currentCallCenterAgent;
// conditional observer method that takes into account:
// - Transaction status, e.g. AFTER_SUCCESS, AFTER_FAILURE
public void trackCustomerCreated(@Observes(during=AFTER_SUCCESS)
@Created Customer newCustomer) {
// track only if agent is in role TRAINEE
if (Role.TRAINEE.equals(currentCallCenterAgent.getRole()){
this.trackAction(TrackAction.CUSTOMER_CREATED, newCustomer)
}
}
...
}
87. #WISSENTEILEN
@ApplicationScoped
public class GlobalCache implements Serializable {
// conditional observer method that takes into account:
// - Bean instance status: ALLWAYS or IF_EXISTS
// - Transaction status, e.g. AFTER_SUCCESS, AFTER_FAILURE
public void insertIntoCache(@Observes(receive=IF_EXISTS,
during=AFTER=SUCCESS)
@Created Customer newCustomer) { ... }
// conditional observer method that takes into account:
// - Bean instance status: ALLWAYS or IF_EXISTS
// - Transaction status, e.g. AFTER_SUCCESS, AFTER_FAILURE
public void insertIntoCache(@Observes(receive=IF_EXISTS,
during=AFTER=SUCCESS)
@Deleted Customer deletedCustomer) { ... }
...
}