Ci sono diversi modi per farlo. Una risposta accurata potrebbe portarci a conoscere più dettagliatamente il design attuale. Tuttavia, ho dovuto fare qualcosa di simile recentemente che ha funzionato bene e mi ha portato meno componenti del previsto.
Prima di tutto, per quanto riguarda le 2 opzioni possibili, il 2 nd mi sembra più pulito perché consente di disaccoppiare il modello attuale dalla nuova funzione. Di solito evito di consentire ai Controller di accedere al livello di persistenza, quindi al momento non andrei in quel modo (# 1).
Come ho commentato, ho implementato qualcosa di simile di recente. Al momento di prendere una decisione, ho deciso di implementare la soluzione meno tipizzata possibile. Con digitato intendo, la soluzione che richiedeva meno classi o interfacce. Questo mi ha portato ad affrontare il problema da un punto di vista convention over configuration . Mi è venuta l'idea di come supportassimo altri formati di rappresentazione come JSON o XML.
L'obiettivo è riutilizzare il modello attuale in modo tale da non dover modificarlo drasticamente, alterarne la gerarchia attuale o avvolgerlo con nuove classi.
Invece di digitare la soluzione con nuove interfacce o classi - che nella maggior parte dei casi non vanno d'accordo con il nostro modello attuale- 3 , usiamo Attributi o Annotazioni.
Visto che non ho familiarità con C #, se non ti dispiace, illustrerò la risposta con Java e @Annotations.
@JacksonXmlRootElement(localName = "kpi-accounts")
@JsonRootName("kpi-accounts")
@XlsxSheetProperty(name = "accounts")
public class KPIAccountsTO implements Serializable {
private static final long serialVersionUID = 1L;
private int active;
private int onBoardingPending;
private int notActive;
// Constructor and setters omitted for brevity ...
@JacksonXmlProperty(localName = "active")
@JsonProperty(value = "active", index = 0)
@XlsxColumnProperty(name = "active", index = 0)
public int getActive() {
return active;
}
@JacksonXmlProperty(localName = "on-boarding-pending")
@JsonProperty(value = "on-boarding-pending", index = 1)
@XlsxColumnProperty(name = "on-boarding-pending", index = 1, widthModifier = 2)
public int getOnBoardingPending() {
return onBoardingPending;
}
@JacksonXmlProperty(localName = "not-active")
@JsonProperty(value = "not-active", index = 2)
@XlsxColumnProperty(name = "not-active", index = 2)
public int getNotActive() {
return notActive;
}
}
Fai attenzione alle annotazioni @XlsxColumnProperty
e @XlsxSheetProperty
. 1
Di solito, non difendo a favore del riflesso ma, in questo caso, funziona come un incantesimo.
Caricamento @XlsxSheetProperty
per la creazione del foglio e dei relativi metadati.
public static XSSFSheet createDefaultSheet(XSSFWorkbook book, Class<?> clazzHeader) {
XlsxSheetProperty sheetAnnotation = clazzHeader.getAnnotationsByType(XlsxSheetProperty.class)[0];
XSSFSheet sheet = book.createSheet(sheetAnnotation.name().toUpperCase());
...
createRowHeder(sheet, clazzHeader);
return sheet;
}
Caricamento @XlsxColumnProperty
per il popolamento dell'intestazione. 2
private static XSSFRow createRowHeder(XSSFSheet sheet, Class<?> clazzHeader) {
XSSFRow header = sheet.createRow(0);
Method[] methods = Arrays.stream(clazzHeader.getMethods())
.filter(method -> method.isAnnotationPresent(XlsxColumnProperty.class))
// Sorted by index
.sorted((m1, m2) -> Integer.compare(
// m1
m1.getAnnotation(XlsxColumnProperty.class).index(),
// m2
m2.getAnnotation(XlsxColumnProperty.class).index()))
.toArray(size -> new Method[size]);
//TODO: Move the stream pipeline
for (int idx = 0; idx < methods.length; idx++) {
XlsxColumnProperty annotation = methods[idx].getAnnotation(XlsxColumnProperty.class);
createDefaultCell(idx, header).setCellValue(annotation.name().toUpperCase());
sheet.setColumnWidth(idx, sheet.getColumnWidth(idx) * annotation.widthModifier());
}
return header;
}
Rimanere da risolvere come mappare i campi entità alle colonne del foglio. L'ordine, come puoi vedere, è già fornito dall'annotazione , quindi l'ultimo per decidere come ottenere i valori. Abbiamo due opzioni qui, mappers o reflection di nuovo. Se l'entità di esportazione è complessa, tendo ad implementare i mapper, per i POJO piatti come quello qui esposto, la riflessione è sufficiente.
Nota : Seguendo questo approccio mi sono permesso di creare cartelle di lavoro con fogli diversi, in cui ognuno di essi è modellato da un POCO diverso.
1: si noti che questo TO appartiene a un modello di dati API. La mia API supporta in realtà diversi formati, tra cui JSON e XML. Tuttavia, potrebbe essere anche il modello di visualizzazione di qualsiasi MVC.
2: Non presti troppa attenzione ai dettagli di implementazione. Sto ancora effettuando il refactoring del codice.
3: il più eterogeneo, il più duro