Общий многослойный шаблон доступа к данным? - PullRequest
2 голосов
/ 04 июня 2010

Я поиграл с некоторыми новыми шаблонами для n-слойного доступа к данным, и наткнулся на один, который кажется очень гибким и простым в реализации. По сути, мне нужно было решение, которое могло бы сделать различные уровни данных подключаемыми / swapabale на лету, то есть базовый доступ к данным из БД, распределенное кэширование, локальное кэширование и т. Д.

Приведенный ниже код легко повторно используется и невероятно эффективен - всего на несколько тактов дольше, чем мое предыдущее полностью жестко закодированное решение.

Как это выглядит? Есть ли способ, чтобы это могло быть реализовано лучше? Есть общие мысли или критика? Любой вклад от тех, кто использовал подобные шаблоны?

Базовый класс:

public class DataAdapterFactory<T> where T : class
{
    private DataAdapter<T> Adapter;
    public DataAdapterFactory(DataAdapterBase<T> Base)
    {
        Adapter = Base;
    }
    public void Extend<U>() where U : DataAdapterExtender<T>, T, new()
    {
        DataAdapterExtender<T> Extender = new U();
        Extender.BaseAdapter = Adapter as T;
        Adapter = Extender;
    }
    public T GetAdapter()
    {
        return Adapter as T;
    }
}
public class DataAdapter<T> where T : class { }
public class DataAdapterBase<T> : DataAdapter<T> where T : class { }
public class DataAdapterExtender<T> : DataAdapter<T> where T : class
{
    public T BaseAdapter;
}

Внедрение в DAL:

// base interface defines methods
public interface IMyDataAdapter
{
    string GetString();
}
// base sql adapter
public class SqlDataAdapter : DataAdapterBase<IMyDataAdapter>, IMyDataAdapter
{
    public string GetString()
    {
        return "SQL";
    }      
}
// provides cache support
public class DistributedCacheExtender : DataAdapterExtender<IMyDataAdapter>, IMyDataAdapter
{
    public string GetString()
    {
        return BaseAdapter.GetString() + ", Distributed Cache";
    }   
}
// provides local cache support
public class LocalCacheExtender : DataAdapterExtender<IMyDataAdapter>, IMyDataAdapter
{
    public string GetString()
    {
        return BaseAdapter.GetString() + ", Local Cache";
    }
}

Доступ к данным:

public IMyDataAdapter GetAdapter() 
{
    // create adapter based on SqlDataAdapter
    DataAdapterFactory<IMyDataAdapter> factory = new DataAdapterFactory<IMyDataAdapter>(new SqlDataAdapter());
    // implement distributed cache
    factory.Extend<DistributedCacheExtender>();
    // implement local cache
    factory.Extend<LocalCacheExtender>();
    return factory.GetAdapter();
}

Используя указанную выше фабрику, любую комбинацию базовых адаптеров и удлинителей (Extend <> () необходимо вызывать в порядке выполнения) можно легко и без проблем использовать на лету через интерфейс, при этом бизнес-уровень ничего не знает о реализация.

В этом случае выше, вызов GetString () приведет к «SQL, распределенный кэш, локальный кэш». В реальном сценарии локальный кэш будет вызван первым. Если элемента там нет, мы бы отправились в распределенный кеш, а если его там нет, мы бы получили его из БД - и любой модуль мог бы быть заменен или вставлен по мере необходимости, в зависимости от объекта. , пользователь и т. д.

Ответы [ 3 ]

1 голос
/ 04 июня 2010

Я бы посмотрел на http://en.wikipedia.org/wiki/Decorator_pattern для этого - ваш пример станет примерно таким:

public interface IMyDataAdapter
{
    string GetString();
}

public class SqlDataAdapter :  IMyDataAdapter
{
    public string GetString()
    {
        return "SQL";
    }      
}

public class LocalCacheDecorator : IMyDataAdapter
{
    private IMyDataAdapter adapter;
    public LocalCacheDecorator(IMyDataAdapter adapter)
    {
        this.adapter = adapter;
    }

    public string GetString()
    {
        return "Local cache, " + this.adapter.GetString();
    }
}
0 голосов
/ 04 июня 2010

Я создал гибридную абстрактную фабрику, которая может служить вашим целям, а также скрывает детали подключения от разработчиков. Он предоставляет набор соединений, в нашем случае нам нужно 4 в каждом наборе. Эти четыре будут возвращены фабрикой «наборов соединений», также существует «фабрика пользовательских наборов соединений, где вы можете изменить соединения, возвращенные в любое время. Вы используете прокси для доступа к объектам подключения. Затем я обращаюсь к прокси-серверу через одноэлементный файл, таким образом, я могу установить его в событии загрузки приложения или в global.asmx, тогда очень легко поменять, какие соединения вы используете. Хотя вы могли бы поменяться во время выполнения. Надеюсь, это поможет.

Это специально написано для моего сценария, так что может быть немного излишним для вас?

Обратите внимание, что это для npgsql, вы можете легко изменить его на стандартные .data. клиентские базовые классы или sqlclent классы ...

    Public MustInherit Class DBConnectionDetail
    'note this abstract class could be an interface if you didn't want these common methods

    Protected _conStrBldr As New Npgsql.NpgsqlConnectionStringBuilder
    Protected _connection As Npgsql.NpgsqlConnection

    Public Sub New()
        'Set the connection builder properties in the subclass
    End Sub

    Friend ReadOnly Property ConnectionStringBuilder() As Npgsql.NpgsqlConnectionStringBuilder
        Get
            Return _conStrBldr
        End Get
    End Property


    Friend Property Connection() As Npgsql.NpgsqlConnection
        Get
            Return _connection
        End Get
        Set(ByVal value As Npgsql.NpgsqlConnection)
            _connection = value
        End Set
    End Property

    ' Misc properties - information for programmers of higher layers
    Public MustOverride ReadOnly Property Description() As String
    Public MustOverride ReadOnly Property HostName() As String
    Public MustOverride ReadOnly Property IP() As String
    Public MustOverride ReadOnly Property Database() As String
End Class



 Public Class LocalArchiveConnectionDetails
    Inherits DBConnectionDetail


    Public Sub New()
        _conStrBldr.Host = "localhost"
        _conStrBldr.Port = 5432
        _conStrBldr.UserName = "usr"
        _conStrBldr.Password = "pwd"
        _conStrBldr.Database = "archive"
        '_conStrBldr.Pooling = True
        '_conStrBldr.MinPoolSize = 5
        '_conStrBldr.MaxPoolSize = 10
        '_conStrBldr.CommandTimeout = 1024
        '_conStrBldr.Timeout = 1024
        '_conStrBldr.ConnectionLifeTime = 2
    End Sub


    Public Overrides ReadOnly Property Description() As String
        Get
            Return "Local Connection to Database"
        End Get
    End Property

    Public Overrides ReadOnly Property Database() As String
        Get
            Return "archive"
        End Get
    End Property

    Public Overrides ReadOnly Property HostName() As String
        Get
            Return "local host"
        End Get
    End Property

    Public Overrides ReadOnly Property IP() As String
        Get
            Return "127.0.0.1"
        End Get
    End Property
End Class

Public Interface IConnectionFactory

    ReadOnly Property GetMasterConnection() As DBConnectionDetail
    ReadOnly Property GetWarehouseConnection() As DBConnectionDetail
    ReadOnly Property GetArchiveConnection() As DBConnectionDetail
    ReadOnly Property GetAuditConnection() As DBConnectionDetail


End Interface

    Public Class DBConnectionBuilder

    Friend Shared Function GetConnection(ByVal conStrBldr As DBConnectionDetail) As NpgsqlConnection
        Return New NpgsqlConnection(conStrBldr.ConnectionStringBuilder.ConnectionString)
    End Function

    'Friend Shared Function GetConnection(ByVal conStr As String) As NpgsqlConnection
    '    Return New NpgsqlConnection(conStr)
    'End Function

End Class

    Public Class LocalConnectionFactory
    Implements IConnectionFactory


    Public ReadOnly Property GetArchiveConnection() As DBConnectionDetail Implements IConnectionFactory.GetArchiveConnection
        Get
            Dim dbConnection As New LocalArchiveConnectionDetails
            dbConnection.Connection = DBConnectionBuilder.GetConnection(dbConnection)
            Return dbConnection
        End Get
    End Property

    Public ReadOnly Property GetMasterConnection() As DBConnectionDetail Implements IConnectionFactory.GetMasterConnection
        Get
            Dim dbConnection As New LocalMasterConnectionDetails
            dbConnection.Connection = DBConnectionBuilder.GetConnection(dbConnection)
            Return dbConnection
        End Get
    End Property

    Public ReadOnly Property GetWarehouseConnection() As DBConnectionDetail Implements IConnectionFactory.GetWarehouseConnection
        Get
            Dim dbConnection As New LocalWarehouseConnectionDetails
            dbConnection.Connection = DBConnectionBuilder.GetConnection(dbConnection)
            Return dbConnection
        End Get
    End Property

    Public ReadOnly Property GetAuditConnection() As DBConnectionDetail Implements IConnectionFactory.GetAuditConnection
        Get
            Dim dbConnection As New LocalAuditConnectionDetails
            dbConnection.Connection = DBConnectionBuilder.GetConnection(dbConnection)
            Return dbConnection
        End Get
    End Property
End Class

 ''' <summary>
''' The custom connection factory allows higher layers to decide which connection will be returned by the connection proxy
''' </summary>
''' <remarks></remarks>
Public Class CustomConnectionFactory
    Implements IConnectionFactory

    Private _archiveConnection As DBConnectionDetail
    Private _masterConnection As DBConnectionDetail
    Private _warehouseConnection As DBConnectionDetail
    Private _auditConnection As DBConnectionDetail

    Friend Sub New()

    End Sub

    Friend Sub New(ByVal masterConnection As DBConnectionDetail, ByVal archiveConnection As DBConnectionDetail, _
                   ByVal warehouseConnection As DBConnectionDetail, ByVal auditConnection As DBConnectionDetail)
        _masterConnection = masterConnection
        _archiveConnection = archiveConnection
        _warehouseConnection = archiveConnection
        _auditConnection = auditConnection
    End Sub

    Friend Sub SetMasterConnectionDetail(ByVal connectionDetail As DBConnectionDetail)
        _masterConnection = connectionDetail
    End Sub
    Friend Sub SetArchiveConnectionDetail(ByVal connectionDetail As DBConnectionDetail)
        _archiveConnection = connectionDetail
    End Sub
    Friend Sub SetWarehouseConnectionDetail(ByVal connectionDetail As DBConnectionDetail)
        _warehouseConnection = connectionDetail
    End Sub
    Friend Sub SetAuditConnectionDetail(ByVal connectionDetail As DBConnectionDetail)
        _auditConnection = connectionDetail
    End Sub

    Public ReadOnly Property GetArchiveConnection() As DBConnectionDetail Implements IConnectionFactory.GetArchiveConnection
        Get
            _archiveConnection.Connection = DBConnectionBuilder.GetConnection(_archiveConnection)
            Return _archiveConnection
        End Get
    End Property

    Public ReadOnly Property GetMasterConnection() As DBConnectionDetail Implements IConnectionFactory.GetMasterConnection
        Get
            _masterConnection.Connection = DBConnectionBuilder.GetConnection(_masterConnection)
            Return _masterConnection
        End Get
    End Property

    Public ReadOnly Property GetWarehouseConnection() As DBConnectionDetail Implements IConnectionFactory.GetWarehouseConnection
        Get
            _warehouseConnection.Connection = DBConnectionBuilder.GetConnection(_warehouseConnection)
            Return _warehouseConnection
        End Get
    End Property

    Public ReadOnly Property GetAuditConnection() As DBConnectionDetail Implements IConnectionFactory.GetAuditConnection
        Get
            _auditConnection.Connection = DBConnectionBuilder.GetConnection(_auditConnection)
            Return _auditConnection
        End Get
    End Property
End Class

 Public Class DBConnectionsProxy

    Private _ConnectionsFactory As IConnectionFactory
    Private _CurrentConnectionsFactory As IConnectionFactory

    Public Sub New(ByVal connectionFactory As IConnectionFactory)
        'check that a connection factory is provided otherwise nothing will work
        If connectionFactory Is Nothing Then
            Throw New NullReferenceException("You must provide a connection factory")
        Else
            _ConnectionsFactory = connectionFactory
            _CurrentConnectionsFactory = connectionFactory
        End If
    End Sub

    Friend Property ConnectionFactory() As IConnectionFactory
        Get
            Return _CurrentConnectionsFactory
        End Get
        Set(ByVal value As IConnectionFactory)
            _CurrentConnectionsFactory = value
        End Set
    End Property

    Public ReadOnly Property GetMasterConnection() As Npgsql.NpgsqlConnection
        Get
            Return _CurrentConnectionsFactory.GetMasterConnection.Connection
        End Get
    End Property

    Public ReadOnly Property GetArchiveConnection() As Npgsql.NpgsqlConnection
        Get
            Return _CurrentConnectionsFactory.GetArchiveConnection.Connection
        End Get
    End Property

    Public ReadOnly Property GetWarehouseConnection() As Npgsql.NpgsqlConnection
        Get
            Return _CurrentConnectionsFactory.GetWarehouseConnection.Connection
        End Get
    End Property

    Public ReadOnly Property GetAuditConnection() As Npgsql.NpgsqlConnection
        Get
            Return _CurrentConnectionsFactory.GetAuditConnection.Connection
        End Get
    End Property


    ''' <summary>
    ''' Reset current connection factory to original connection factory this proxy was instantiated with
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub ResetConnection()
        _CurrentConnectionsFactory = _ConnectionsFactory
    End Sub

    ''' <summary>
    ''' Changes the master connection returned for the current connection factory
    ''' </summary>
    ''' <param name="connectionDetail">Connection information for master database</param>
    ''' <remarks></remarks>
    Public Sub SetMasterConnection(ByVal connectionDetail As DBConnectionDetail)
        Me.SetAllConnections(connectionDetail, _CurrentConnectionsFactory.GetArchiveConnection, _
                             _CurrentConnectionsFactory.GetWarehouseConnection, _CurrentConnectionsFactory.GetAuditConnection)
    End Sub

    ''' <summary>
    ''' Changes the archive connection returned for the current connection factory
    ''' </summary>
    ''' <param name="connectionDetail">Connection information for archive database</param>
    ''' <remarks></remarks>
    Public Sub SetArchiveConnection(ByVal connectionDetail As DBConnectionDetail)
        Me.SetAllConnections(_CurrentConnectionsFactory.GetMasterConnection, connectionDetail, _
                             _CurrentConnectionsFactory.GetWarehouseConnection, _CurrentConnectionsFactory.GetAuditConnection)
    End Sub

    ''' <summary>
    ''' Changes the warehouse connection returned for the current connection factory
    ''' </summary>
    ''' <param name="connectionDetail">Connection information for warehouse database</param>
    ''' <remarks></remarks>
    Public Sub SetWarehouseConnection(ByVal connectionDetail As DBConnectionDetail)
        Me.SetAllConnections(_CurrentConnectionsFactory.GetMasterConnection, _CurrentConnectionsFactory.GetArchiveConnection, _
                             connectionDetail, _CurrentConnectionsFactory.GetAuditConnection)
    End Sub

    ''' <summary>
    ''' Changes the audit connection returned for the current connection factory
    ''' </summary>
    ''' <param name="connectionDetail">Connection information for audit database</param>
    ''' <remarks></remarks>
    Public Sub SetAuditConnection(ByVal connectionDetail As DBConnectionDetail)
        Me.SetAllConnections(_CurrentConnectionsFactory.GetMasterConnection, _CurrentConnectionsFactory.GetArchiveConnection, _
                             _CurrentConnectionsFactory.GetWarehouseConnection, connectionDetail)
    End Sub


    ''' <summary>
    ''' Sets the current connection factory to a custom connection factory using the supplied connection
    ''' </summary>
    ''' <param name="masterConnectionDetail">Connection information for master database</param>
    ''' <param name="archiveConnectionDetail">Connection information for archive database</param>
    ''' <param name="warehouseConnectionDetail">Connection information for warehouse database</param>
    ''' <remarks></remarks>
    Public Sub SetAllConnections(ByVal masterConnectionDetail As DBConnectionDetail, _
                                 ByVal archiveConnectionDetail As DBConnectionDetail, _
                                 ByVal warehouseConnectionDetail As DBConnectionDetail, _
                                 ByVal auditConnectionDetail As DBConnectionDetail)

        Dim customConnFactory As New CustomConnectionFactory
        customConnFactory.SetMasterConnectionDetail(masterConnectionDetail)
        customConnFactory.SetArchiveConnectionDetail(archiveConnectionDetail)
        customConnFactory.SetWarehouseConnectionDetail(warehouseConnectionDetail)
        customConnFactory.SetAuditConnectionDetail(auditConnectionDetail)

        _CurrentConnectionsFactory = customConnFactory
    End Sub


End Class
0 голосов
/ 04 июня 2010

То, что вы делаете, выглядит достаточно разумно, хотя было бы полезно увидеть, как классы связаны с точки зрения того, в каких пространствах имен и в каких сборках они находятся.

Мой опыт заключается в абстрагировании поставщиков данных за интерфейсами, когда поставщики данных (а иногда и интерфейсы) живут в отдельной сборке (так: 1 для BL, 1 для интерфейсов и 1 для каждого из поставщиков данных) ).

Я определяю интерфейсы вокруг бизнес-концепций (таких как IPageDataProvider, ICustomerDataProvider и т. Д.), А не источники данных (например, таблицы db), и вы хотите помнить о принципе разделения интерфейса при разработке интерфейсов.

Я загружаю нужный поставщик данных во время выполнения через фабрику; для этого используется метод Activator.CreateInstance . Фабрика получает инструкции из конфига.

Итак, когда я хочу использовать данные в своей бизнес-логике, я создаю экземпляр требуемой реализации интерфейса через фабрику (одна строка кода).

При таком подходе нет базового класса, который поставщики могут использовать , но, очевидно, вы можете использовать базовые классы внутри поставщика данных, если хотите.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...