使用尽可能少的更改动态地(在运行时)更改持久层

时间:2015-08-31 20:26:07

标签: java scala design-patterns dependency-injection persistence

我正在寻找一种设计模式/方式来动态地交换我的应用程序的(持久性)层(最好甚至在运行时)。

为什么?

我希望能够决定是将某些数据保存到XML还是将数据保存在“每个实例”的基础上。所以我可能会认为一个项目使用XML作为后端而另一个项目使用数据库。我想在这里灵活,并且能够轻松添加另一个“驱动程序”,例如杰森或其他什么。

现在假设以下设置:

我们有一个控制器,我们想管理一些数据。我们可以在SQL和XML实现之间进行选择。

一种可能的(工作)解决方案:

BasicController.scala

val myPersistenceLayer: PersistenceLayer = SQLPersistenceLayer

val apples: Seq[Apple] = myPersistenceLayer.getApples()

trait PersistenceLayer
{
    def getApples(): Seq[Apple]
    def getBananas(): Seq[Banana]
}

object SQLPersistenceLayer extends PersistenceLayer
{
    override def getApples(): Seq[Apple] = {...}
    override def getBananas(): Seq[Banana] = {...}
}

这是一个相当令人讨厌的解决方案,因为必须为每个新模型(想想水果!;))添加方法,不仅在特征中,而且在每个实现中。我喜欢我的单一责任,所以我宁愿将其委托给模特,比如:

trait PersistenceLayer
{
    def getAll(model: Model): Seq[Model] = { model.getAll() }
}

trait Model
{
    def getAll(): Seq[Model]
}

package "SQL"

class Apple extends Model
{
    def getAll(): Seq[Apple] = { // do some SQL magic here }
}

package "XML"

class Apple extends Model
{
    def getAll(): Seq[Apple] = { // do some XML magic here instead }
}

现在最大的问题是,即使我实现了具体的PersistenceLayer,就像这样:

object SQLPersistenceLayer extends PersistenceLayer {}

我怎么能告诉应用程序使用正确包的模型?

如果我使用SQLPersistenceLayer:

val apples = myPersistenceLayer.get(Apple) 

我需要导入正确的“Apple”类,这会破坏整个目的,因为我可以删除所有其他类,导入正确的类,只需使用通用的“getAll()”方法。

所以我再次需要在多行改变实现,这是我想要避免的。

我想过给类似于包名的字符串,比如

val package =“sql”并在控制器中从正确的包中导入它,但这不是真的可行而且不是很容易实现,而且对于我显然缺少的东西来说这是一个相当讨厌的黑客。

简而言之:我希望能够切换程序包以用于动态的持久性需求。在一些动态类型的语言中,我可以提出一个解决方案,但不是Scala或任何静态类型的语言,所以我想我不知道这里的某种设计模式

**编辑**

一个想法发生了(是的,有时会发生;))现在我想知道这样的事情是否会导致我想要的东西:

namespace tld.app.persistence

trait PersistenceLayer
{
    proteced val models: mutable.HashMap[String, Model] = new mutable.HashMap[String, Model]

    def registerModel(key: String, model: Model): Unit =
    {
        models.remove(key)
        models.put(key, model)
    }

    def get(model: String): Seq[Future[Model]] =
    {
        val m: Model = models.getOrElse(model, throw new Exception("No such model found!"))
        m.get
    }   
}

trait Model
{
    def get(): Seq[Future[Model]]
}

namespace tld.app.persistence.sql

object SQLPersistenceLayer extends PersistenceLayer

class Person extends Model
{
    def get(): Seq[Future[Model]] =
    {
        // ... query the database
    }
}

namespace tld.app.persistence.xml

object XMLPersistenceLayer extends PersistenceLayer

class Person extends Model
{
    def get(): Seq[Future[Model]] =
    {
        // ... read in from the appropriate xml-file
    }
}

object Settings
{
    var persistenceLayer: PersistenceLayer = SQLPersistenceLayer // Default is SQLPersistenceLayer
}

Somewhere in the application:

Settings.persistenceLayer.get("person")

// Then a user-interaction happens

Settings.persistenceLayer = XMLPersistenceLayer

Settings.persistenceLayer.get("person")

persistenceLayer 通常保持不变,但用户可以决定更改它。只要我能找到时间,我就会深入了解它。但也许有人会立即发现这种方法存在问题。

5 个答案:

答案 0 :(得分:3)

DI允许您在编译时连接实现。在Scala中有很多方法可以做DI(Cake Pattern,Reader Monad,DI frameworks等)。

如果要在应用程序启动时连接依赖关系,那么常规依赖机制将起作用。您只需根据某些条件创建所需依赖项(SQL,XML)的实例,并将其传递给代码。

如果你想在应用程序执行期间不断切换依赖关系,即有时你保存到SQL,有时候保存到XML,那么你可以使用类似于Lift Injector的东西,另请参阅我的回答here - 选项2。

答案 1 :(得分:2)

您可以使用运行时反射来完成它。您需要在运行时指定并创建类/对象,然后将其传递给Persistency层,然后只调用泛型getAll方法。

有关反射库的详细信息 - > http://docs.scala-lang.org/overviews/reflection/overview.html

最好为每个持久层创建具有Apple方法的伴随对象getAll

然后使用完整包名称

访问带有反射的Apple对象
val apple:sql.Apple = //Reflection library object access
val apple:xml.Apple = //Reflection library object access


val apples = myPersistenceLayer.get(apple)

答案 2 :(得分:2)

我认为你可以用implicits + TypeTags实现基于模块的包含

object SqlPersistence {
  implicit def getAll[T: TypeTag](): Seq[T] = {/* type-based sql implementation*/}
}

object JsonPersistence {
  implicit def getAll[T: TypeTag](): Seq[T] = {/* type-based json implementation*/}
}

object PersistenceLayer {
  def getAll[T](implicit getter: Unit => Seq[T]): Seq[T] = getter
}

// somewhere else ...
import SqlPersistence._

PersistenceLayer.getAll[Apple]

优点是您可以通过引入相应的导入来确定现场的持久层。主要的缺点是相同的:你需要在每次调用时决定你的持久层,并确保它是你的想法。此外,根据我的个人经验,编译器对于棘手的隐含极端情况不太有帮助,因此有可能花费更多时间进行调试。

如果您为应用设置一次持久层,那么DI就可以了,例如cake pattern。但话说回来,你要么需要每个类都有一个方法,要么求助于反思。没有反思,它可能看起来像这样:

trait PersistenceLayer {
  def getApples(): Apples
}

trait SqlPersistenceLayer extends PersistenceLayer {
  override def getApples() = // sql to get apples 
}

trait Controller {
  this: PersistenceLayer =>

  def doMyAppleStuff = getApples()
}

// somewhere in the main ...
val controller = new Controller with SqlPersistence {}
controller.doMyAppleStuff

答案 3 :(得分:2)

类似的东西是strategy pattern,如果有帮助的话。

答案 4 :(得分:-1)

我认为存储库模式是您的解决方案。

修改

确定。谢谢" -1"那很好,因为我没有解释我的想法...

  

我的例子只是众多其他人中的一个。所以我希望这对那里的人有用

我将尝试解释有关使用存储库和工厂模式的想法。

为此我创建了一个带有示例代码的github存储库:https://github.com/StefanHeimberg/stackoverflow-32319416

我的设置与您的问题几乎相同。但不同之处如下:

  • 我没有使用scala。但概念是一样的......
  • 我的设置只包含"标记"对于存储库工厂。
  • "模型"对象是持久性的无知。这意味着不知道如何坚持。这是存储库的关注
  • 我手动进行依赖注入,因为这对于示例来说应该足够了
  • 我没有" Controller"但我有#34;应用服务" ...

关于所使用的实现的决定是在每次调用create()方法时在工厂内部进行的。

域层对所使用的基础架构实现一无所知。应用程序层正在编排域服务和基础结构服务(在我的示例中只是存储库)

如果您有任何DI容器,那么工厂可以由生产者或其他人......取决于DI容器

包结构:

enter image description here

我也做了一个简单的集成测试

public class AppleServiceIT {

    private Settings settings;
    private AppleService appleService;

    @Before
    public void injectDependencies() {
        settings = new Settings();
        final JdbcAppleRepository jdbcAppleRepository = new JdbcAppleRepository();
        final JsonAppleRepository jsonAppleRepository = new JsonAppleRepository();
        final AppleRepositoryFactory appleRepositoryFactory = new AppleRepositoryFactory(jdbcAppleRepository, jsonAppleRepository);
        appleService = new AppleService(settings, appleRepositoryFactory);
    }

    @Test
    public void test_findAppleById() {
        // test with jdbc
        settings.setRepositoryType(RepositoryTypeEnum.JDBC);
        assertEquals("JDBC-135", appleService.findAppleById(135l).getMessage());

        // test with json
        settings.setRepositoryType(RepositoryTypeEnum.JSON);
        assertEquals("JSON-243", appleService.findAppleById(243l).getMessage());
    }

    @Test
    public void test_getApples() {
        // test with jdbc
        settings.setRepositoryType(RepositoryTypeEnum.JDBC);
        assertEquals(2, appleService.getApples().size());

        // test with json
        settings.setRepositoryType(RepositoryTypeEnum.JSON);
        assertEquals(3, appleService.getApples().size());
    }

}