Design Pattern para importação de dados de vários tipos de origem e para vários tipos de destino

14

Eu tenho que projetar e criar um script de importação (em C #) que possa lidar com o seguinte:

  • ler dados de várias fontes (XML, XSLX, CSV)
  • Verificar dados
  • escreva os dados para vários tipos de objetos (cliente, endereço)

Os dados virão de várias fontes, mas uma fonte sempre terá um formato de importação (csv, xml, xslx). Os formatos de importação podem variar de fonte para fonte. Novos formatos de importação podem ser adicionados no futuro. Os tipos de objetos de destino são sempre os mesmos (cliente, endereço e um pouco mais).

Eu tenho pensado em usar genéricos e li algo sobre o padrão de fábrica, mas sou um noob bastante grande nessa área, então qualquer conselho é mais que bem-vindo.

Qual é um padrão de design apropriado para resolver esse problema?

jao
fonte
Mantenha simples.
NoChance

Respostas:

11

Você está exagerando com conceitos extravagantes era muito cedo. Genéricos - quando você vê um caso, use-os, mas não se preocupe. Padrão de fábrica - flexibilidade demais (e confusão adicional) para isso ainda.

Mantenha simples. Use práticas fundamentais.

  1. Tente imaginar as coisas comuns entre fazer uma leitura para XML, uma leitura para CSV, qualquer que seja. Coisas como, próximo registro, próxima linha. Como Novos formatos podem ser adicionados, tente imaginar pontos comuns que o formato a ser determinado teria com os conhecidos. Use essa semelhança e defina uma 'interface' ou contrato que todos os formatos devem aderir. Embora adiram ao terreno comum, todos eles podem ter suas regras internas específicas.

  2. Para validar os dados, tente fornecer uma maneira de conectar facilmente blocos de códigos validadores novos ou diferentes. Então, novamente, tente definir uma interface em que cada validador, responsável por um tipo específico de construção de dados, adira a um contrato.

  3. Para criar as construções de dados, você provavelmente será restringido por quem projetar os objetos de saída sugeridos mais do que qualquer coisa. Tente descobrir qual é o próximo passo para os objetos de dados e há alguma otimização que você possa fazer sabendo o uso final. Por exemplo, se você souber que os objetos serão usados ​​em um aplicativo interativo, poderá ajudar o desenvolvedor desse aplicativo fornecendo 'somatórios' ou contagens dos objetos ou outros tipos de informações derivadas.

Eu diria que a maioria destes são padrões de modelo ou padrões de estratégia. Todo o projeto seria um padrão de adaptador.

Andyz Smith
fonte
+1, especialmente no primeiro parágrafo (e é bom ver que você chegou à mesma conclusão que eu no último parágrafo).
Doc Brown
Lembre-se também da arquitetura de todo o projeto, para adaptar um formato a outro. Você pode imaginar alguma situação em que alguém possa usar apenas uma parte disso em outro projeto? Talvez um novo validador de dados esteja disponível no mercado e funcione apenas com o SQL server. Então agora você só quer ler o XML personalizado e colocar no SQL Server, pulando o restante das etapas.
Andyz Smith
Para facilitar isso, não apenas as peças devem ter seus contratos internos aos quais aderem, mas também um conjunto de contratos que definem a interação entre as peças .
Andyz Smith
@AndyzSmith - Eu tenho o mesmo problema no meu código. Eu entendi tudo sobre o seu código, exceto o padrão do adaptador. Quando você disse que o projeto inteiro é um exemplo de padrão de adaptador, pode ilustrar isso?
gansub
9

O óbvio é aplicar o padrão de estratégia . Tenho uma classe base genérico ReadStrategye para cada formato de entrada uma subclasse como XmlReadStrategy, CSVReadStrategyetc. Isso permitirá que você mude o processamento de importação de forma independente a partir do processamento VERIFICAÇÃO eo processamento de saída.

Dependendo dos detalhes, também pode ser possível manter a maioria das partes da importação genérica e trocar apenas partes do processamento de entrada (por exemplo, leitura de um registro). Isso pode levar você ao padrão do método de modelo .

Doc Brown
fonte
Isso significa que, ao usar o padrão de estratégia, tenho que criar métodos separados para converter os objetos (cliente, endereço) da origem para o destino. O que eu gostaria de fazer é ler, converter e validar cada objeto e colocá-lo em uma lista para que a lista possa ser salva posteriormente no banco de dados.
jao
@jao: bem, se você ler minha resposta novamente, verá que minha sugestão era criar o "ReadStrategy", não um "ConvertStrategy". Portanto, você só precisa escrever métodos diferentes para ler objetos (ou qualquer parte adicional do seu processo é individual para o formato de arquivo específico).
Doc Brown
7

Um padrão adequado para um utilitário de importação que talvez você precise estender no futuro seria usar o MEF - você pode manter o uso de memória baixo carregando o conversor necessário em tempo real a partir de uma lista lenta, criar importações de MEF decoradas com atributos que ajudam a selecionar o conversor certo para a importação que você está tentando executar e fornece uma maneira fácil de separar as diferentes classes de importação.

Cada parte do MEF pode ser construída para satisfazer uma interface de importação com alguns métodos padrão que convertem uma linha do arquivo de importação em seus dados de saída ou substituem uma classe base pela funcionalidade básica.

O MEF é uma estrutura para criar uma arquitetura de plug-in - é como o Outlook e o Visual Studio são criados, todas essas extensões adoráveis ​​no VS são partes do MEF.

Para criar um aplicativo MEF (Managed Extensability Framework), comece incluindo uma referência a System.ComponentModel.Composition

Definir interfaces para especificar o que o conversor fará

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

Isso pode ser usado para todos os tipos de arquivos que você deseja importar.

Adicione atributos a uma nova classe que defina o que a classe "Exportará"

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

Isso definiria uma classe que importará arquivos CSV (de um formato específico: Format1) e possui atributos personalizados que configuram os metadados do atributo de exportação MEF. Você repetiria isso para cada formato ou tipo de arquivo que deseja importar. Você pode definir atributos personalizados com uma classe 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 realmente usar os conversores MEF, você precisa importar as partes que você cria quando o código de conversão é executado:

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

catalog coleta as partes de uma pasta, o padrão é o local do aplicativo.

converters é uma lista lenta das peças importadas do MEF

Então, quando você souber que tipo de arquivo deseja converter ( importFileTypee importType), obtenha um conversor na lista de peças importadas emconverters

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);
....
}

A chamada para converter.ImportDatausará o código na classe importada.

Pode parecer muito código e pode demorar um pouco para você entender o que está acontecendo, mas é extremamente flexível quando se trata de adicionar novos tipos de conversor e pode até permitir que você adicione novos durante o tempo de execução.

Matt
fonte
Eu nunca ouvi falar do MEF antes. O que é isso?
jao
2
@jao confira o link para obter uma explicação completa. Adicionamos alguns exemplos de material do MEF à minha resposta.
22813 Matt
1
Esta é uma excelente maneira de começar o MEF. 1
paqogomez
MEF é uma tecnologia, não um padrão de design. Não, -1porque a ideia subjacente ainda faz sentido e depende de um padrão de estratégia governado pela IImportConverterinterface.
GETah
0

Qual é um padrão de design apropriado para resolver esse problema?

Os idiomas C # envolvem o uso da estrutura de serialização incorporada para fazer isso. Você anota os objetos com metadados e, em seguida, instancia serializadores diferentes que usam essas anotações para extrair dados para colocar na forma correta ou vice-versa.

Os formulários XML, JSON e binários são os mais comuns, mas não ficaria surpreso se outros já existirem em um bom formulário compactado para você consumir.

Telastyn
fonte
Bem, isso funciona bem se você estiver livre para usar seu próprio formato de arquivo, mas acho que essa abordagem falhará em formatos complexos e predefinidos como XSLX, o que significa arquivos do MS Excel no formato XML compactado.
Doc Brown
Posso mapear uma linha de um arquivo do Excel para um objeto, mas precisaria copiar e adaptar esse método aos leitores XML e CSV. E eu gostaria de manter o código mais limpo possível ...
jao
@docBrown - howso? Conceitualmente, transformar um objeto em uma série de células no Excel não é realmente diferente de transformá-lo em um documento xml.
Telastyn 22/08/2013
@ Telastyn: você diz que pode usar a estrutura de serialização incorporada da estrutura .NET para ler o formato XLSX? Se isso fosse verdade, bibliotecas como o Open XML SDK ou NPOI estavam obsoletas.
Doc Brown
@docbrown: minhas desculpas, você está correto - Eu continuo esquecendo que não há nenhuma classe base serializer comum já que é uma das primeiras coisas que é feito em qualquer trabalho codebase I no.
Telastyn