paneles - Redux/Java: gestión de datos normizados y representaciones de modelos múltiples por entidad
table swing java (5)
Probablemente no haya una respuesta correcta, solo qué estrategia prefiere:
La estrategia más simple es agregar otra pieza a su reductor llamada producto
selectedProduct
y siempre sobrescribirlo con el objeto completo del producto seleccionado actualmente. Su modal siempre mostraría los detalles delselectedProduct
. La caída de esta estrategia es que no está almacenando en caché los datos en el caso cuando un usuario selecciona el mismo producto por segunda vez, y sus campos mínimos no están normalizados.O puede actualizar la instancia en su tienda de productos como dijo, solo necesitará la lógica para manejarlo. Cuando selecciona un producto, si está completamente cargado, renderízalo. Si no, haga la llamada ajax y muestre un spinner hasta que esté completamente cargado.
Estamos construyendo una nueva aplicación usando React / Redux que se procesa en el servidor.
Queremos seguir las mejores prácticas para Redux y normalizar nuestros datos en el servidor antes de que pasen al estado inicial de la tienda.
Para este ejemplo, digamos que tenemos una entidad genérica de "Productos" que puede ser bastante compleja y está normalizada en la raíz de nuestro estado de tienda y nivel de página en otro objeto en la raíz de la tienda. Entonces, la estructura y los Reductores siguen el patrón típico de ''reductor de rodajas'' y se verá así:
{
page_x_state: PageReducer
products: ProductsReducer
}
Estamos utilizando reductores de combinación para fusionar los reductores antes de pasarlos a la tienda.
Caso de uso teórico: Tenemos una página de ''productos'' que muestra una lista de información básica del producto. Un usuario puede hacer clic en un producto para mostrar un modal que luego carga y muestra los datos completos del producto.
Para el ejemplo anterior, el estado enviado desde el servidor contendrá solo modelos de productos básicos (3 o 4 campos), esto es suficiente para renderizar la tabla y obtener toda la información del producto en este punto es un desperdicio y no es muy eficiente.
Cuando un usuario hace clic en un producto, realizaremos una llamada AJAX para obtener todos los datos de ese producto. Una vez que tengamos todos los datos para el producto único, ¿debemos actualizar la instancia en la tienda de productos con un modelo completo? Si es así, terminaríamos con un conjunto de objetos que podrían ser estados diferentes (algunos podrían tener campos mínimos frente a algunos que son objetos completos con 10s de campos). ¿Es esta la mejor manera de manejarlo?
Además, me gustaría escuchar cualquier idea sobre cómo administrar diferentes representaciones del mismo modelo subyacente en el servidor y cómo asignarlo a la tienda Redux (en Java, idealmente).
Puede almacenar cada entidad como un objeto de sus diversas representaciones. En el creador de acciones que actualiza la entidad, incluya la representación como argumento:
const receiveProducts = (payload = [], representation = ''summary'') => ({
type: ''PRODUCTS_RECEIVED'',
payload, representation
});
const productReducer = (state = {}, action) => {
case ''PRODUCTS_RECEIVED'': {
const { payload, representation } = action;
return {
...state,
...payload.reduce((next, entity) => {
next[entity.id] = {
...next[entity.id],
[representation]: entity
};
return next
}, {})
}
}
};
Esto significa que quien llame a receiveProducts()
necesita saber qué representación se devuelve.
Si no le preocupa almacenar datos adicionales en la tienda redux, en realidad no afectará mucho su rendimiento si usa un estado normalizado. Entonces, en ese frente, recomendaría el almacenamiento en caché todo lo que pueda sin arriesgar la seguridad.
Creo que la mejor solución para usted sería usar algún middleware redux para que a su front end no le importe cómo se obtienen los datos. Enviará una acción a la tienda redux y el middleware puede determinar si necesita o no una llamada AJAX para obtener los nuevos datos. Si necesita recuperar los datos, entonces el middleware puede actualizar el estado cuando el AJAX se resuelva; si no lo hace, puede descartar la acción porque ya tiene los datos. De esta forma, puede aislar el problema de tener dos representaciones diferentes de los datos en el middleware e implementar una resolución allí para que su front-end simplemente solicite datos y no le importe cómo lo obtiene.
No conozco todos los detalles de la implementación, por lo que Jeff dijo que probablemente sea más lo que prefieres, pero definitivamente recomendaría agregar algún middleware para manejar tus llamadas AJAX, si no lo hiciste deberías hacer que la interacción con la tienda sea mucho más simple.
Si desea leer más sobre middleware, la documentación de Redux es bastante buena.
deberíamos actualizar la instancia en la tienda de productos con un modelo completo
Cabe señalar que Java y ReactJs + Redux no tienen mucha superposición conceptual. Todo es un objeto Javascript, no un objeto con una clase.
En general, almacenar todos los datos que recibe en el estado de la tienda Redux es el camino a seguir. Para evitar el hecho de que algunos de los datos serán mínimos y algunos estarán completamente cargados, debe realizar una llamada ajax condicional en el método onComponentWillMount
del contenedor de visualización del producto individual.
class MyGreatProduct extends React.Component {
onComponentWillMount() {
if(!this.props.thisProduct.prototype.hasProperty( ''somethingOnlyPresentInFullData'' )) {
doAjaxCall(this.props.thisProduct.id).then((result) => {
this.props.storeNewResult(result.data);
}).catch(error=>{ ... })
}
}
// the rest of the component container code
}
const mapStateToProps = (state, ownProps) => {
return {
thisProduct: state.products.productInfo[ownProps.selectedId] || {id: ownProps.selectedId}
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
storeNewResult: (data) => { dispatch(productDataActions.fullProductData(data)) }
}
export default connect(mapStateToProps, mapDispatchToProps)(MyGreatProduct);
Con este código, debe quedar claro qué tan agnósticos pueden ser los componentes y los contenedores con respecto a los datos exactos disponibles en la Tienda en cualquier momento dado.
Editar: en términos de administrar diferentes representaciones del mismo modelo subyacente en el servidor y cómo asignarlo a la tienda de Redux, trataría de utilizar la misma holgura relativa con la que está tratando una vez que tenga JSON. Esto debería eliminar algunos acoplamientos.
Lo que quiero decir con esto es solo agregar los datos que tiene a un JSObject para ser consumido por React + Redux, sin preocuparse demasiado por los valores que potencialmente podrían almacenarse en el estado de Redux durante la ejecución de la aplicación.
EDITAR:
Respondiendo explícitamente a su primera pregunta, si sus reductores están construidos correctamente, su árbol de estado completo debe inicializarse sin absolutamente ningún dato. Pero debe ser la forma correcta. Sus reductores siempre deben tener un valor de retorno predeterminado, cuando se renderiza el lado del servidor, Redux solo debería representar el estado inicial.
Después de la representación del lado del servidor, cuando la tienda (que ahora está en el lado del cliente) necesita actualización debido a una acción del usuario, la forma del estado de todos sus datos de producto ya está allí (es solo que algunos de ellos probablemente serán valores predeterminados). . En lugar de sobreescribir un objeto, simplemente rellene los espacios en blanco, por así decirlo.
Digamos que en su vista de segundo nivel necesita el name
, photo_url
, price
y brand
y la vista inicial tiene 4 productos, su tienda renderizada se vería así:
{
products: {
by_id: {
"1": {
id: "1",
name: "Cool Product",
tags: [],
brand: "Nike",
price: 1.99,
photo_url: "http://url.com",
category: "",
product_state: 0,
is_fetching: 0,
etc: ""
},
"2": {
id: "2",
name: "Another Cool Product",
tags: [],
brand: "Adidas",
price: 3.99,
photo_url: "http://url2.com",
category: "",
product_state: 0,
is_fetching: 0,
etc: ""
},
"3": {
id: "3",
name: "Crappy Product",
tags: [],
brand: "Badidas",
price: 0.99,
photo_url: "http://urlbad.com",
category: "",
product_state: 0,
is_fetching: 0,
etc: ""
},
"4": {
id: "4",
name: "Expensive product",
tags: [],
brand: "Rolex",
price: 199.99,
photo_url: "http://url4.com",
category: "",
product_state: 0,
is_fetching: 0,
etc: ""
}
},
all_ids: ["1", "2", "3", "4"]
}
}
Puede ver en los datos anteriores que algunas claves son solo cadenas vacías o una matriz vacía. Pero tenemos nuestros datos que necesitamos para la representación inicial real de la página.
Podríamos hacer llamadas asincrónicas al cliente en segundo plano inmediatamente después de que el servidor haya procesado y el documento esté listo; es probable que el servidor devuelva esas llamadas iniciales antes de que el usuario intente obtener los datos de todos modos. A continuación, podemos cargar productos posteriores a petición del usuario. No creo que sea el mejor enfoque, pero es el que tiene más sentido para mí. Algunas otras personas pueden tener algunas otras ideas. Depende completamente de su aplicación y caso de uso.
Sin embargo, solo mantendría un objeto de producto en estado y conservaría TODOS los datos relacionados con los productos.
Recientemente implementé una aplicación en producción y compartiré algunas de mis ideas. La aplicación, aunque no era demasiado grande, tenía una estructura de datos compleja y había pasado por todo el proceso como novato en producción de Redux (y con la orientación de mi arquitecto). Estos son algunos de nuestros puntos de partida. No hay una forma correcta en términos de arquitectura, pero ciertamente hay algunas cosas que evitar o hacer.
1. Antes de disparar por escrito, sus reductores diseñan un estado "estático"
Si no sabe hacia dónde se dirige, no puede llegar allí. Escribir toda la estructura de su estado en plano le ayudará a razonar sobre cómo su estado cambiará con el tiempo. Descubrimos que esto nos ahorró tiempo porque no tuvimos que reescribir realmente grandes secciones.
2. Diseñando tu estado
mantenlo simple. El objetivo de Redux es simplificar la administración del estado. Usamos muchos de los consejos de los tutoriales de egghead.io en Redux que fueron creados por Dan Abramov. Están claros realmente ayudaron a resolver muchos problemas que estábamos enfrentando. Estoy seguro de que ha leído los documentos sobre la normalización del estado, pero los ejemplos simples que dieron realmente se llevaron a cabo en la mayoría de los patrones de datos que implementamos.
En lugar de crear redes web complejas de datos, cada fragmento de datos solo contenía sus propios datos si era necesario hacer referencia a otra parte de los datos , solo hacía referencia a ellos por medio de un ID . Encontramos que este patrón simple cubría la mayoría de nuestras necesidades.
{
products: {
by_id: {
"1": {
id: "1",
name: "Cool Product",
tags: ["tag1", "tag2"],
product_state: 0,
is_fetching: 0,
etc: "etc"
}
},
all_ids: ["1"]
}
}
En el ejemplo anterior, las etiquetas pueden ser otro fragmento de datos con una estructura de datos similar utilizando by_id
y all_ids
. En todos los documentos y tutoriales, Abramov sigue haciendo referencia a los datos relacionales y las bases de datos relacionales. Esto fue realmente clave para nosotros. Al principio seguimos mirando la interfaz de usuario y diseñando nuestro estado sobre cómo pensamos que íbamos a mostrarlo. Cuando esto hizo clic y comenzamos a agrupar los datos en función de su relación con otras piezas de datos, las cosas comenzaron a hacer clic en su lugar.
Pasando rápidamente a su pregunta, evitaría la duplicación de datos, como se menciona en otro comentario, personalmente simplemente crearía una clave en el objeto de estado denominado product_modal
. deja que el modal se encargue de su propio estado ...
{
products: {
...
},
product_modal: {
current_product_id: "1",
is_fetching: true,
is_open: true
}
}
Descubrimos que seguir este patrón con el estado de la página también funcionaba muy bien ... lo tratamos como cualquier otro dato con una identificación / nombre, etc.
3. Lógica del reductor
asegúrese de que los reductores sigan su propio estado . muchos de nuestros reductores se parecían bastante similares, al principio esto parecía un DRY infernal, pero luego nos dimos cuenta rápidamente del poder de más reductores ... digamos que una acción se despacha y desea actualizar un pedazo completo de estado ... no hay problema, simplemente revise en tu reductor para la acción y devuelve el nuevo estado. Si solo desea actualizar uno o dos campos en el mismo estado ... entonces solo hace lo mismo, pero solo en los campos que desea cambiar. la mayoría de nuestros reductores eran simplemente una declaración de cambio con una declaración if anidada de vez en cuando.
Combinación de Reductores
No usamos combineReducers, escribimos el nuestro. No fue difícil, nos ayudó a entender lo que estaba pasando en Redux, y nos permitió ser un poco más inteligentes con nuestro estado. Este tut fue invaluable
Comportamiento
Middleware es tu amigo ... usamos fetch API con redux-thunk para hacer solicitudes RESTful. Dividimos las solicitudes de datos requeridas en acciones separadas que llamaban a store.dispatch () para cada fragmento de datos que necesitaba actualizarse para la llamada. Cada envío envió otra acción para actualizar el estado. Esto mantuvo nuestro estado actualizado de forma modular y nos permitió actualizar secciones grandes, o granular según sea necesario.
Tratando con una API
De acuerdo, hay demasiado para tratar aquí. No digo que nuestro camino sea el mejor ... pero nos ha funcionado. En resumen ... tenemos una API interna en Java con puntos finales públicamente expuestos. Las llamadas de esta API no siempre se asignaban a la interfaz fácilmente. No hemos implementado esto, pero idealmente, se podría haber escrito un punto de init
inicial para obtener una serie de datos iniciales que se necesitaban para que las cosas avanzaran por adelantado.
Creamos una API pública en el mismo servidor que la aplicación, escrita en PHP. Esta API abstraía los puntos finales de la API interna (y en algunos casos también los datos) lejos de la interfaz y el navegador.
Cuando la aplicación realizaba una solicitud GET a /api/projects/all
la API PHP llamaba a nuestra API interna, obtenía los datos necesarios (a veces en un par de solicitudes) y devolvía esos datos en un formato utilizable que redux podía consumir.
Este podría no ser el enfoque ideal para una aplicación de JavaScript, pero no teníamos la opción de crear una nueva estructura de API interna, necesitábamos usar una que haya existido durante varios años, hemos encontrado que el rendimiento es aceptable.