diff --git a/force_bdss/base_core_driver.py b/force_bdss/base_core_driver.py
index 5875f1784dd0db5af54adb5b5901aeb869aca1a0..4d7a9429ad24be5a7315406286a10527b4f52cca 100644
--- a/force_bdss/base_core_driver.py
+++ b/force_bdss/base_core_driver.py
@@ -5,6 +5,8 @@ from force_bdss.bundle_registry_plugin import (
     BundleRegistryPlugin,
     BUNDLE_REGISTRY_PLUGIN_ID
 )
+from force_bdss.io.workflow_reader import WorkflowReader
+from force_bdss.workspecs.workflow import Workflow
 
 
 class BaseCoreDriver(Plugin):
@@ -14,5 +16,13 @@ class BaseCoreDriver(Plugin):
 
     bundle_registry = Instance(BundleRegistryPlugin)
 
+    #: Deserialized content of the workflow file.
+    workflow = Instance(Workflow)
+
     def _bundle_registry_default(self):
         return self.application.get_plugin(BUNDLE_REGISTRY_PLUGIN_ID)
+
+    def _workflow_default(self):
+        reader = WorkflowReader(self.bundle_registry)
+        with open(self.application.workflow_filepath) as f:
+            return reader.read(f)
diff --git a/force_bdss/bdss_application.py b/force_bdss/bdss_application.py
index c813e4dc4357c922bc356ef48f076adfc70966be..dd26fa617daaf027842c18577e24987d026b966d 100644
--- a/force_bdss/bdss_application.py
+++ b/force_bdss/bdss_application.py
@@ -11,8 +11,6 @@ from force_bdss.core_mco_driver import CoreMCODriver
 
 from traits.api import Unicode, Bool, Instance
 
-from force_bdss.workspecs.workflow import Workflow
-
 
 class BDSSApplication(Application):
     """Main application for the BDSS.
@@ -22,9 +20,6 @@ class BDSSApplication(Application):
     #: The path of the workflow file to open
     workflow_filepath = Unicode()
 
-    #: Deserialized content of the workflow file.
-    workflow = Instance(Workflow)
-
     #: This flags signals to the application not to execute and orchestrate
     #: the MCO, but instead to perform a single evaluation under the
     #: coordination of the MCO itself. See design notes for more details.
@@ -56,7 +51,3 @@ class BDSSApplication(Application):
             print("No extensions found")
 
         super(BDSSApplication, self).__init__(plugins=plugins)
-
-    def _workflow_default(self):
-        with open(self.workflow_filepath) as f:
-            return Workflow.from_json(json.load(f))
diff --git a/force_bdss/core_evaluation_driver.py b/force_bdss/core_evaluation_driver.py
index 19e56664c580721246ee42ad65197512ccad8e0a..d7cc54c6aadb70953636fe5944ec56ffd912a884 100644
--- a/force_bdss/core_evaluation_driver.py
+++ b/force_bdss/core_evaluation_driver.py
@@ -1,6 +1,9 @@
-from traits.has_traits import on_trait_change
+import sys
+from traits.api import on_trait_change
 
 from force_bdss.base_core_driver import BaseCoreDriver
+from force_bdss.io.workflow_reader import InvalidVersionException, \
+    InvalidFileException
 
 
 class CoreEvaluationDriver(BaseCoreDriver):
@@ -10,11 +13,14 @@ class CoreEvaluationDriver(BaseCoreDriver):
 
     @on_trait_change("application:started")
     def application_started(self):
-        workflow = self.application.workflow
-
-        mco_data = workflow.multi_criteria_optimizer
-        mco_bundle = self.bundle_registry.mco_bundle_by_id(mco_data.id)
-        mco_model = mco_bundle.create_model(mco_data.model_data)
+        try:
+            workflow = self.workflow
+        except (InvalidVersionException, InvalidFileException) as e:
+            print(str(e), file=sys.stderr)
+            sys.exit(1)
+
+        mco_model = workflow.multi_criteria_optimizer
+        mco_bundle = mco_model.bundle
         mco_communicator = mco_bundle.create_communicator(
             self.application,
             mco_model)
@@ -22,22 +28,17 @@ class CoreEvaluationDriver(BaseCoreDriver):
         parameters = mco_communicator.receive_from_mco()
 
         ds_results = []
-        for requested_ds in workflow.data_sources:
-            ds_bundle = self.bundle_registry.data_source_bundle_by_id(
-                requested_ds.id)
-            ds_model = ds_bundle.create_model(requested_ds.model_data)
-            data_source = ds_bundle.create_data_source(
-                self.application, ds_model)
+        for ds_model in workflow.data_sources:
+            ds_bundle = ds_model.bundle
+            data_source = ds_bundle.create_data_source(self.application,
+                                                       ds_model)
             ds_results.append(data_source.run(parameters))
 
         kpi_results = []
-        for requested_kpic in workflow.kpi_calculators:
-            kpic_bundle = self.bundle_registry.kpi_calculator_bundle_by_id(
-                requested_kpic.id)
-            ds_model = kpic_bundle.create_model(
-                requested_kpic.model_data)
-            kpi_calculator = kpic_bundle.create_data_source(
-                self.application, ds_model)
+        for kpic_model in workflow.kpi_calculators:
+            kpic_bundle = kpic_model.bundle
+            kpi_calculator = kpic_bundle.create_kpi_calculator(
+                self.application, kpic_model)
             kpi_results.append(kpi_calculator.run(ds_results))
 
         mco_communicator.send_to_mco(kpi_results)
diff --git a/force_bdss/core_mco_driver.py b/force_bdss/core_mco_driver.py
index f84c83dda83f6eeee1f7839608060d61551c952f..b4e613539526b678b791b5ac6e5f750cb3438efb 100644
--- a/force_bdss/core_mco_driver.py
+++ b/force_bdss/core_mco_driver.py
@@ -5,7 +5,7 @@ import sys
 from traits.api import on_trait_change
 
 from force_bdss.base_core_driver import BaseCoreDriver
-from force_bdss.workspecs.workflow import (InvalidVersionException,
+from force_bdss.io.workflow_reader import (InvalidVersionException,
                                            InvalidFileException)
 
 
@@ -17,14 +17,12 @@ class CoreMCODriver(BaseCoreDriver):
     @on_trait_change("application:started")
     def application_started(self):
         try:
-            workflow = self.application.workflow
+            workflow = self.workflow
         except (InvalidVersionException, InvalidFileException) as e:
             print(str(e), file=sys.stderr)
             sys.exit(1)
 
-        mco_data = workflow.multi_criteria_optimizer
-        mco_bundle = self.bundle_registry.mco_bundle_by_id(mco_data.id)
-        mco_model = mco_bundle.create_model(mco_data.model_data)
+        mco_model = workflow.multi_criteria_optimizer
+        mco_bundle = mco_model.bundle
         mco = mco_bundle.create_optimizer(self.application, mco_model)
-
         mco.run()
diff --git a/force_bdss/data_sources/base_data_source_model.py b/force_bdss/data_sources/base_data_source_model.py
index d88d3adff6f7938b163a3369a38612864a9a2132..f3ebea6f3b3d5a28d1d3a22ca0501a769bc473c5 100644
--- a/force_bdss/data_sources/base_data_source_model.py
+++ b/force_bdss/data_sources/base_data_source_model.py
@@ -1,9 +1,11 @@
-import abc
-from traits.api import ABCHasStrictTraits
+from traits.api import ABCHasStrictTraits, Instance
+
+from .i_data_source_bundle import IDataSourceBundle
 
 
 class BaseDataSourceModel(ABCHasStrictTraits):
-    @classmethod
-    @abc.abstractmethod
-    def from_json(self, model_data):
-        pass
+    bundle = Instance(IDataSourceBundle)
+
+    def __init__(self, bundle, *args, **kwargs):
+        self.bundle = bundle
+        super(BaseDataSourceModel, self).__init__(self, *args, **kwargs)
diff --git a/force_bdss/io/workflow_reader.py b/force_bdss/io/workflow_reader.py
new file mode 100644
index 0000000000000000000000000000000000000000..eaad270a398cdea05e94331bbce7ccb88e09a449
--- /dev/null
+++ b/force_bdss/io/workflow_reader.py
@@ -0,0 +1,60 @@
+import json
+
+from traits.api import HasStrictTraits, Instance
+
+from ..workspecs.workflow import Workflow
+from ..bundle_registry_plugin import BundleRegistryPlugin
+
+SUPPORTED_FILE_VERSIONS = ["1"]
+
+
+class InvalidFileException(Exception):
+    pass
+
+
+class InvalidVersionException(InvalidFileException):
+    pass
+
+
+class WorkflowReader(HasStrictTraits):
+    bundle_registry = Instance(BundleRegistryPlugin)
+
+    def __init__(self, bundle_registry, *args, **kwargs):
+        self.bundle_registry = bundle_registry
+
+        super(WorkflowReader, self).__init__(*args, **kwargs)
+
+    def read(self, file):
+        json_data = json.load(file)
+        registry = self.bundle_registry
+
+        try:
+            version = json_data["version"]
+        except KeyError:
+            raise InvalidFileException("Corrupted input file, no version"
+                                       " specified")
+
+        if version not in SUPPORTED_FILE_VERSIONS:
+            raise InvalidVersionException(
+                "File version {} not supported".format(json_data["version"]))
+
+        wf = Workflow()
+
+        mco_id = json_data["multi_criteria_optimizer"]["id"]
+        mco_bundle = registry.mco_bundle_by_id(mco_id)
+        wf.multi_criteria_optimizer = mco_bundle.create_model(
+            json_data["multi_criteria_optimizer"]["model_data"])
+
+        for ds_entry in json_data["data_sources"]:
+            ds_id = ds_entry["id"]
+            ds_bundle = registry.data_source_bundle_by_id(ds_id)
+            wf.data_sources.append(ds_bundle.create_model(
+                ds_entry["model_data"]))
+
+        for kpic_entry in json_data["kpi_calculators"]:
+            kpic_id = kpic_entry["id"]
+            kpic_bundle = registry.kpi_calculator_bundle_by_id(kpic_id)
+            wf.kpi_calculators.append(kpic_bundle.create_model(
+                kpic_entry["model_data"]))
+
+        return wf
diff --git a/force_bdss/kpi/base_kpi_calculator_model.py b/force_bdss/kpi/base_kpi_calculator_model.py
index 9489b9b89ded2d98442b5f72052e2666673952a7..512be3e12a38cc5329c97eda20ea22ef90146cc6 100644
--- a/force_bdss/kpi/base_kpi_calculator_model.py
+++ b/force_bdss/kpi/base_kpi_calculator_model.py
@@ -1,9 +1,11 @@
-import abc
-from traits.has_traits import ABCHasStrictTraits
+from traits.api import ABCHasStrictTraits, Instance
+
+from .i_kpi_calculator_bundle import IKPICalculatorBundle
 
 
 class BaseKPICalculatorModel(ABCHasStrictTraits):
-    @classmethod
-    @abc.abstractmethod
-    def from_json(self, model_data):
-        pass
+    bundle = Instance(IKPICalculatorBundle)
+
+    def __init__(self, bundle, *args, **kwargs):
+        self.bundle = bundle
+        super(BaseKPICalculatorModel, self).__init__(self, *args, **kwargs)
diff --git a/force_bdss/mco/base_mco_model.py b/force_bdss/mco/base_mco_model.py
index 3bf128cfb0f7132eed980a92cd8d39770233a097..b2630459ef0ec2dbe2a035a5e866cf675ac077a2 100644
--- a/force_bdss/mco/base_mco_model.py
+++ b/force_bdss/mco/base_mco_model.py
@@ -1,9 +1,11 @@
-import abc
-from traits.api import ABCHasStrictTraits
+from traits.api import ABCHasStrictTraits, Instance
+
+from .i_multi_criteria_optimizer_bundle import IMultiCriteriaOptimizerBundle
 
 
 class BaseMCOModel(ABCHasStrictTraits):
-    @classmethod
-    @abc.abstractmethod
-    def from_json(self, model_data):
-        pass
+    bundle = Instance(IMultiCriteriaOptimizerBundle)
+
+    def __init__(self, bundle, *args, **kwargs):
+        self.bundle = bundle
+        super(BaseMCOModel, self).__init__(self, *args, **kwargs)
diff --git a/force_bdss/workspecs/data_source.py b/force_bdss/workspecs/data_source.py
deleted file mode 100644
index bad5752c63bdf9bb9b3d76256592d9ec875c53c4..0000000000000000000000000000000000000000
--- a/force_bdss/workspecs/data_source.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from traits.api import HasStrictTraits, String, Dict
-
-
-class DataSource(HasStrictTraits):
-    id = String()
-    model_data = Dict()
-
-    @classmethod
-    def from_json(cls, json_data):
-        self = cls(
-            id=json_data["id"],
-            model_data=json_data["model_data"]
-        )
-
-        return self
diff --git a/force_bdss/workspecs/kpi_calculator.py b/force_bdss/workspecs/kpi_calculator.py
deleted file mode 100644
index b8066259538af6b8e19168d4404a432e3f2bdadc..0000000000000000000000000000000000000000
--- a/force_bdss/workspecs/kpi_calculator.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from traits.api import HasStrictTraits, String, Dict
-
-
-class KPICalculator(HasStrictTraits):
-    id = String()
-    model_data = Dict()
-
-    @classmethod
-    def from_json(cls, json_data):
-        self = cls(
-            id=json_data["id"],
-            model_data=json_data["model_data"]
-        )
-
-        return self
diff --git a/force_bdss/workspecs/multi_criteria_optimizer.py b/force_bdss/workspecs/multi_criteria_optimizer.py
deleted file mode 100644
index efa4bfd9428e7c8e6a4d093649b0fc07d79c81a8..0000000000000000000000000000000000000000
--- a/force_bdss/workspecs/multi_criteria_optimizer.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from traits.api import HasStrictTraits, String, Dict
-
-
-class MultiCriteriaOptimizer(HasStrictTraits):
-    id = String()
-    model_data = Dict()
-
-    @classmethod
-    def from_json(cls, json_data):
-        self = cls(
-            id=json_data["id"],
-            model_data=json_data["model_data"]
-        )
-
-        return self
diff --git a/force_bdss/workspecs/workflow.py b/force_bdss/workspecs/workflow.py
index edc85c80f6f1770b08a15ce3dba17aa9080d61e7..6ece6c7dc8db29498e973074b6b4de370a143030 100644
--- a/force_bdss/workspecs/workflow.py
+++ b/force_bdss/workspecs/workflow.py
@@ -1,48 +1,12 @@
 from traits.api import HasStrictTraits, Instance, String, List
 
-from force_bdss.workspecs.data_source import DataSource
-from force_bdss.workspecs.kpi_calculator import KPICalculator
-from .multi_criteria_optimizer import MultiCriteriaOptimizer
-
-SUPPORTED_FILE_VERSIONS = ["1"]
-
-
-class InvalidFileException(Exception):
-    pass
-
-
-class InvalidVersionException(InvalidFileException):
-    pass
+from ..data_sources.base_data_source_model import BaseDataSourceModel
+from ..kpi.base_kpi_calculator_model import BaseKPICalculatorModel
+from ..mco.base_mco_model import BaseMCOModel
 
 
 class Workflow(HasStrictTraits):
     name = String()
-    multi_criteria_optimizer = Instance(MultiCriteriaOptimizer)
-    data_sources = List(DataSource)
-    kpi_calculators = List(KPICalculator)
-
-    @classmethod
-    def from_json(cls, json_data):
-        try:
-            version = json_data["version"]
-        except KeyError:
-            raise InvalidFileException("Corrupted input file, no version"
-                                       " specified")
-
-        if version not in SUPPORTED_FILE_VERSIONS:
-            raise InvalidVersionException(
-                "File version {} not supported".format(json_data["version"]))
-
-        self = cls(
-            multi_criteria_optimizer=MultiCriteriaOptimizer.from_json(
-                json_data["multi_criteria_optimizer"]
-            ),
-            data_sources=[
-                DataSource.from_json(data_source_data)
-                for data_source_data in json_data["data_sources"]],
-            kpi_calculators=[
-                KPICalculator.from_json(kpi_calculator_data)
-                for kpi_calculator_data in json_data["kpi_calculators"]]
-        )
-
-        return self
+    multi_criteria_optimizer = Instance(BaseMCOModel)
+    data_sources = List(BaseDataSourceModel)
+    kpi_calculators = List(BaseKPICalculatorModel)