Desarrollo de arquitectura multiplataforma
Unificación de interfaces Web, Móvil
y Escritorio
Ignacio Baca Moreno-Torres
Alberto Mateos Checa
Alberto Jimenez Ruiz
Enero 2012
Introducción y planteamiento del problema
Idea Inicial
Aplicación de localización de surfistas
- ¿Donde esta la gente navegando?
- Diario de sesiones
La aplicación debe tener los siguientes UI
- Interfaz móvil
- Interfaz web
- Interfaz escritorio
Casos de uso usuario móvil
Casos de uso usuario web
Casos de uso usuario escritorio
Objetivos
- Crear una interfaz comun
- Máximo desacoplamiento entre clientes
- Aplicación de metodologías de desarrollo
Arquitectura del Sistema
Modulos del sistema
Artefactos del sistema (maven)
Modelo de datos del sistema
Servidor que debe hacer y como se ha implementado
¿Que debe hacer el servidor?
- Registrar el usuario y su posición
- Obtener listas de playas, localizadas y activas (hotspot)
- Generar evenews y notificarlos
- Mantener un diarío de sesiones
Modulos del servidor
Separación de responsabilidad , fachada de sesion
Separación de responsabilidad , capa de servicios
Separación de responsabilidad , capa de persistencia
Modulos, paquetes y clases
- Sesion y otras utilidades
org.inftel.socialwind.server
SocialWindRequestFactoryServlet, UserNotAuthenticatedException, Utils...
- Servicios
org.inftel.socialwind.server.services
SurferService, SessionsService, SpotService, EnvewsService...
- Persistencia
org.inftel.socialwind.server.domain
DomainContext, BaseEntity, Surfer, Spot, Session...
Nunca reinventar la rueda, usar herramientas disponibles
- Uso de genericos
- Uso de JSE, ThreadLocal
- Uso de java, reimplementación anonima
- Correcto tratamiento de excepciones
private static ThreadLocal<EntityManager> holder =
new ThreadLocal<EntityManager>() {
@Override
protected EntityManager initialValue() {
throw new IllegalStateException(DomainContext.class.getName()
+ " no esta inicializado");
}
};
org.inftel.socialwind.server.domain.DomainContext
Desarrollo centrado en test
- Primero, definición del API
- Test validaban el api (integración)
- Test validan las funcionalidad de cada modulo (unitario)
- Se implementa el código
@Test(expected = UserNotAuthenticatedException.class)
public final void testCurrentUserNotAuthenticated() {
helper.setEnvIsLoggedIn(false);
@SuppressWarnings("unused")
Surfer current = SurferService.currentSurfer();
}
...socialwind.server.services.SurferServiceTest (main/test)
Cliente de Escritorio java SE y Swing
¿Que hace?
- Guardar y obtener propiedades de usuario
- Autenticarse en google
- Autorizar el uso del servidor*
- Modificar datos de usuario
en el servidor
- Recibir notificaciones del
servidor
Arquitectura de la aplicación Swing
Persistencia, local y remota
- Persistencia local
java.util.prefs.Preferences
Preferences preferences = Preferences.userNodeForPackage(ApplicationWindow.class);
userName = preferences.get("user_name", "");
password = preferences.get("password", "");
savePassword = preferences.getBoolean("save_password", false);
- Persistencia remota
A través del API SocialWind
SurferRequest request = requestFactory.surferRequest();
surfer = request.edit(surfer);
surfer.setDisplayName(getDisplayName());
surfer.setFullName(getFullName());
request.fire(new Receiver...
Sincronización de evenews
Dos threads, uno para comunicación y otro para mostrar
// Blocking queue para esperar recibir mensajes
private final BlockingQueue<String[]> queue = new ArrayBlockingQueue<String[]>(10);
// offer, por si hay demasiados que se descarten
queue.offer(new String[] { title, message });
// take para que quede bloqueado hasta que reciba algo
String[] data = queue.take();
Cliente móvil
Por: Alberto Mateos Checa
Estructura de paquetes
MVC: Android vs JRE
- No existe método Main
- No está implementado el patrón Observer
- Definición de los elementos de la UI mediante XML
MVC: Modelo
Uso de 3 modelos: hotspots, sesiones y perfil.
Hotspots
- ArrayList<Spot> (nombre, descripción, número de surfers actuales y totales, longitud, latitud, url de imagen)
Sesiones
- ArrayList<Session> (spot, fecha inicio, fecha fin)
Perfil
- DisplayName, FullName, GravatarHash
MVC: Modelo
MVC: Modelo
Características
MVC: Modelo
MVC: Modelo
MVC: Controlador
- Contienen un objeto modelo como dato miembro
- Definen funciones para recuperar datos del modelo o actualizarlo
MVC: Vista
MVC: Vista
MVC: Vista
Patrones utilizados
Tareas asíncronas
Objetivo: no congelar la UI
Uso de la clase AsyncTask
- Obtención y actualización de datos en el servidor
- Descarga de la imagen de perfil desde Gravatar
Client Web google web toolkit
Requisitos del Cliente Web
Objetivos del Cliente Web
- Facilidad para añadir nueva funcionalidad.
- Facilidad para probar el modelo.
- Facilidad de su mantenimiento.
Necesidad de una buena arquitectura:
Arquitectura del Cliente Web
Patrón Factory
Guarda Instancias estáticas de objetos pesados:
- Comunicación con el servidor.
- Bus de eventos.
- Vistas.
public class ClientFactoryImpl implements ClientFactory {
/** Bus encargado de gestionar los distintos eventos de la aplicacion */
private static EventBus eventBus;
/** Vista encargada de mostrar el listados de playas */
private static PlayasListView playasListView;
/** Vista encargada de mostrar el perfil */
private static PerfilView perfilView;
/** Objeto necesario para realizar la comunicacion con el servidor */
private static SocialwindRequestFactory swrf;
/** Objeto necesario para realizar los cambios de actividad */
private static PlaceController placeController;
Patrón Activity/Place
Se encarga de realizar el cambio entre actividades. Una actividad es un componente MVP.
@UiHandler("opIntro")
void showPanelIntroduccion(ClickEvent event) {
clientFactory.getPlaceController().goTo(new IntroduccionPlace());
}
public Activity getActivity(Place place) {
if (place instanceof PlayasListPlace)
return new PlayasListActivity((PlayasListPlace) place, clientFactory, false);
else if (place instanceof PerfilPlace)
return new PerfilActivity((PerfilPlace) place, clientFactory);
.....
}
Patrón Model-View-Presenter
Lleva a cabo la separación de responsabilidades.
La parte Presenter:
- Se encarga de la lógica de negocio.
- Se comunica con el servidor para traer Proxys del modelo.
- Se comunica con la parte View para que pinte los componentes.
La parte View:
- Recibe ordenes del Presenter para pintar componentes gráficos.
- Sólo se crea la primera vez.
Patrón Model-View-Presenter
La parte Model:
- Son los objetos que residen en el servidor.
- Se envían a través de objetos proxy serializados.
public void onCargarListadoPlayas() {
SpotRequest sr = swrf.spotRequest();
sr.findAllSpots().with("location").fire(new Receiver>() {
public void onSuccess(List response) {
playasListView.addPlayas(response);
}
public void onFailure(ServerFailure error) {
System.out.println(error.getMessage());
}
});
}
Componentes visuales
Separación de Funcionalidad:
Componentes visuales
Estructura de un componente:
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:style>
</ui:style>
<g:HTMLPanel>
<h2>Listado de Playas:</h2>
<g:FlowPanel ui:field="fpPlayas"></g:FlowPanel>
</g:HTMLPanel>
</ui:UiBinder>
Componentes visuales
Funcionalidad de un componente:
public class SesionListViewImpl extends Composite implements SesionListView {
private static SesionListImplUiBinder uiBinder = GWT.create(SesionListImplUiBinder.class);
interface SesionListImplUiBinder extends UiBinder {}
@UiField FlowPanel fpsesiones;
Internacionalización
Con esta arquitectura internacionalizar textos es muy simple:
- Los textos se encapsulan con la etiqueta <ui:msg>.
<ui:msg>Panel Principal del ejemplo</ui:msg>
- GWT crea un archivo properties con los textos del componente.
- Hay que incluir el módulo de Internacionalización.
<module>
<inherits name='com.google.gwt.i18n.I18N'/>
<extend-property name='locale' values='en_US, es'/>
<set-property-fallback name='locale' value='es'/>
</module>
Dudas
Gracias!