Patrón de diseño para la importación de datos de varios tipos de origen y a varios tipos de destino

14

Tengo que diseñar y construir un script de importación (en C #) que pueda manejar lo siguiente:

  • leer datos de varias fuentes (XML, XSLX, CSV)
  • verifique los datos
  • escribir los datos en varios tipos de objetos (cliente, dirección)

Los datos provendrán de varias fuentes, pero una fuente siempre tendrá un formato de importación (ya sea csv, xml, xslx). Los formatos de importación pueden variar de una fuente a otra. Se pueden agregar nuevos formatos de importación en el futuro. Los tipos de objetos de destino son siempre los mismos (cliente, direcciones y algunos más).

He estado pensando en usar genéricos y leí algo sobre el patrón de fábrica, pero soy un novato bastante grande en esta área, por lo que cualquier consejo es más que bienvenido.

¿Cuál es un patrón de diseño apropiado para resolver este problema?

jao
fuente
Mantenlo simple.
NoChance

Respuestas:

11

Estás yendo por la borda con conceptos sofisticados fue demasiado pronto. Genéricos: cuando vea un caso, úselos, pero de lo contrario no se preocupe. Patrón de fábrica: demasiada flexibilidad (y confusión adicional) para esto todavía.

Mantenlo simple. Utiliza prácticas fundamentales.

  1. Trate de imaginar las cosas comunes entre hacer una lectura para XML, una lectura para CSV lo que sea. Cosas como, siguiente disco, siguiente línea. Dado que se pueden agregar nuevos formatos, trate de imaginar la similitud que el formato a determinar tendría con los conocidos. Utilice esta característica común y defina una 'interfaz' o un contrato al que deben adherirse todos los formatos. Aunque se adhieren al terreno común, todos pueden tener sus reglas internas específicas.

  2. Para validar los datos, intente proporcionar una forma de conectar fácilmente bloques de códigos de validación nuevos o diferentes. Nuevamente, intente definir una interfaz donde cada validador, responsable de un tipo particular de construcción de datos, se adhiera a un contrato.

  3. Para crear las construcciones de datos, probablemente estará limitado por quien diseñe los objetos de salida sugeridos más que nada. Intente averiguar cuál es el siguiente paso para los objetos de datos, y ¿hay alguna optimización que pueda hacer al conocer el uso final? Por ejemplo, si sabe que los objetos se utilizarán en una aplicación interactiva, podría ayudar al desarrollador de esa aplicación al proporcionar 'sumas' o recuentos de los objetos u otro tipo de información derivada.

Yo diría que la mayoría de estos son patrones de plantilla o patrones de estrategia. Todo el proyecto sería un patrón Adaptador.

Andyz Smith
fuente
+1, especialmente para el primer párrafo (y es bueno ver que llegaste a la misma conclusión que yo en el último párrafo).
Doc Brown
También tenga en cuenta la arquitectura de todo el proyecto, para adaptar un formato a otro. ¿Te imaginas alguna situación en la que alguien pueda usar solo una parte de eso en otro proyecto? EG Quizás un nuevo validador de datos salga al mercado y funcione solo con el servidor SQL. Así que ahora solo quiere leer el XML personalizado y ponerlo en el servidor SQL, omitiendo el resto de los pasos.
Andyz Smith
Para facilitar esto, no solo las piezas deben tener sus contratos internos a los que se adhieran, sino que debe haber un conjunto de contratos que definan la interacción entre las piezas .
Andyz Smith
@AndyzSmith: tengo el mismo problema en mi código. Entendí todo acerca de su código, excepto el patrón del adaptador. Cuando dijiste que todo el proyecto es un ejemplo de patrón Adaptador, ¿puedes ilustrar eso?
gansub
9

Lo obvio es aplicar el patrón de estrategia . Tenga una clase base genérica ReadStrategyy para cada formato de entrada una subclase como XmlReadStrategy, CSVReadStrategyetc. Esto le permitirá cambiar el procesamiento de importación independientemente del procesamiento de verificación y el procesamiento de salida.

Dependiendo de los detalles, también es posible mantener la mayoría de las partes de la importación genérica e intercambiar solo partes del procesamiento de entrada (por ejemplo, la lectura de un registro). Esto puede llevarlo al patrón Método de plantilla .

Doc Brown
fuente
¿Significa que cuando uso el patrón de estrategia, tengo que crear métodos separados para convertir los objetos (cliente, dirección) desde el origen al destino. Lo que me gustaría hacer es leer, convertir y validar cada objeto y ponerlo en una lista para que la lista se pueda guardar más tarde en la base de datos.
jao
@jao: bueno, si vuelves a leer mi respuesta, verás que mi sugerencia fue crear "ReadStrategy", no una "ConvertStrategy". Por lo tanto, solo tiene que escribir diferentes métodos para leer objetos (o cualquier parte adicional de su proceso es individual para el formato de archivo específico).
Doc Brown
7

Un patrón adecuado para una utilidad de importación que puede necesitar extender en el futuro sería usar MEF: puede mantener bajo el uso de memoria cargando el convertidor que necesita sobre la marcha desde una lista perezosa, crear importaciones MEF decoradas con atributos que ayudan a seleccionar el convertidor adecuado para la importación que está intentando realizar y proporciona una manera fácil de separar las diferentes clases de importación.

Cada parte MEF se puede construir para satisfacer una interfaz de importación con algunos métodos estándar que convierten una fila del archivo de importación a sus datos de salida o anulan una clase base con la funcionalidad básica.

MEF es un marco para crear una arquitectura de complemento: es cómo se construyen Outlook y Visual Studio, todas esas extensiones encantadoras en VS son partes de MEF.

Para crear una aplicación MEF (Managed Extensability Framework) comience por incluir una referencia a System.ComponentModel.Composition

Defina interfaces para especificar qué hará el convertidor

public interface IImportConverter
{
    int UserId { set; }        
    bool Validate(byte[] fileData, string fileName, ImportType importType);
    ImportResult ImportData(byte[] fileData, string fileName, ImportType importType);
}

Esto se puede usar para todos los tipos de archivos que desea importar.

Agregue atributos a una nueva clase que defina lo que la clase "Exportará"

[Export(typeof(IImportConverter))]
[MyImport(ImportType.Address, ImportFileType.CSV, "4eca4a5f-74e0")]
public class ImportCSVFormat1 : ImportCSV, IImportConverter
{
 ...interface methods...
}

Esto definiría una clase que importará archivos CSV (de un formato particular: Format1) y tiene atributos personalizados que configuran Metadatos de atributos de exportación MEF. Repita esto para cada formato o tipo de archivo que desee importar. Puede establecer atributos personalizados con una clase como:

[MetadataAttribute]
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
public class ImportAttribute : ExportAttribute
{
    public ImportAttribute(ImportType importType, ImportFileType fileType, string customerUID)
        : base(typeof(IImportConverter))
    {
        ImportType = importType;
        FileType = fileType;
        CustomerUID = customerUID;
    }

    public ImportType ImportType { get; set; }
    public ImportFileType FileType { get; set; }
    public string CustomerUID { get; set; }
}

Para usar realmente los convertidores MEF, debe importar las partes MEF que crea cuando se ejecuta su código de conversión:

[ImportMany(AllowRecomposition = true)]
protected internal Lazy<IImportConverter, IImportMetadata>[] converters { get; set; }
AggregateCatalog catalog = new AggregateCatalog();

catalog recopila las partes de una carpeta, el valor predeterminado es la ubicación de la aplicación.

converters es una lista perezosa de las partes MEF importadas

Luego, cuando sepa qué tipo de archivo desea convertir ( importFileTypey importType) obtenga un convertidor de la lista de partes importadas enconverters

var tmpConverter = (from x in converters
                    where x.Metadata.FileType == importFileType
                    && x.Metadata.ImportType == importType 
                    && (x.Metadata.CustomerUID == import.ImportDataCustomer.CustomerUID)
                    select x).OrderByDescending(x => x.Metadata.CustomerUID).FirstOrDefault();

if (tmpConverter != null)
{
     var converter = (IImportConverter)tmpConverter.Value;
     result = converter.ImportData(import.ImportDataFile, import.ImportDataFileName, importType);
....
}

La llamada a converter.ImportDatautilizará el código en la clase importada.

Puede parecer una gran cantidad de código y puede llevar un tiempo entender qué está pasando, pero es extremadamente flexible cuando se trata de agregar nuevos tipos de convertidor e incluso puede permitirle agregar nuevos durante el tiempo de ejecución.

Mate
fuente
No he oído hablar de MEF antes. ¿Qué es?
jao
2
@jao echa un vistazo al enlace para obtener una explicación completa. Agregué algunas cosas de ejemplo MEF a mi respuesta.
Matt
1
Esta es una excelente manera de comenzar en MEF. +1
paqogomez
MEF es una tecnología, no un patrón de diseño. No -1, porque la idea subyacente todavía tiene sentido y se basa en un patrón de estrategia regido por la IImportConverterinterfaz.
GETah
0

¿Cuál es un patrón de diseño apropiado para resolver este problema?

Los modismos de C # implican usar el marco de serialización incorporado para hacer esto. Anota los objetos con metadatos y luego crea una instancia de diferentes serializadores que usan esas anotaciones para extraer datos para ponerlos en la forma correcta, o viceversa.

Xml, JSON y las formas binarias son las más comunes, pero no me sorprendería si ya existen otras en un buen paquete para que las consumas.

Telastyn
fuente
Bueno, esto funciona bien si es libre de usar su propio formato de archivo, pero supongo que este enfoque fallará para formatos complejos y predefinidos como XSLX, lo que significa archivos MS Excel en formato XML comprimido.
Doc Brown
Puedo asignar una línea de un archivo de Excel a un objeto, pero necesitaría copiar y adaptar ese método a los lectores XML y CSV. Y me gustaría mantener el código lo más limpio posible ...
jao
@docBrown - ¿cómo? Conceptualmente, convertir un objeto en una serie de celdas en Excel no es realmente diferente de convertirlo en un documento xml.
Telastyn
@Telastyn: ¿dices que puedes usar el marco de serialización incorporado del marco .NET para leer el formato XLSX? Si eso fuera cierto, las bibliotecas como Open XML SDK o NPOI eran obsoletas.
Doc Brown
@docbrown: mis disculpas, tienes razón: sigo olvidando que no hay una clase base de serializador común, ya que esa es una de las primeras cosas que se hace en cualquier base de código en la que trabajo.
Telastyn