diff --git a/.coveragerc b/.coveragerc
index baa10de454893675aeedc6275e2c6725b0b84966..69c1dcd3f1ca5068733a54fdb231bab80170169d 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -23,3 +23,6 @@ exclude_lines =
 
     # Don't complain about import statements
     import
+
+    # Don't complain about abstract class declarations and placeholders
+    pass
diff --git a/src/datastore.py b/src/datastore.py
new file mode 100644
index 0000000000000000000000000000000000000000..348f28549e716a30605ff2be5894fd5b03b4dc95
--- /dev/null
+++ b/src/datastore.py
@@ -0,0 +1,331 @@
+__author__ = 'Lukas Leufen'
+__date__ = '2019-11-22'
+
+
+from typing import Any, List, Tuple
+
+from abc import ABC
+
+
+class NameNotFoundInDataStore(Exception):
+    """
+    Exception that get raised if given name is not found in the entire data store.
+    """
+    pass
+
+
+class NameNotFoundInScope(Exception):
+    """
+    Exception that get raised if given name is not found in the provided scope, but can be found in other scopes.
+    """
+    pass
+
+
+class EmptyScope(Exception):
+    """
+    Exception that get raised if given scope is not part of the data store.
+    """
+    pass
+
+
+class AbstractDataStore(ABC):
+
+    """
+    Data store for all settings for the experiment workflow to save experiment parameters for the proceeding modules
+    and predefine parameters loaded during the experiment setup phase. The data store is hierarchically structured, so
+    that global settings can be overwritten by local adjustments.
+    """
+    def __init__(self):
+        # empty initialise the data-store variables
+        self._store = {}
+
+    def put(self, name: str, obj: Any, scope: str) -> None:
+        """
+        Abstract method to add an object to the data store
+        :param name: Name of object to store
+        :param obj: The object itself to be stored
+        :param scope: the scope / context of the object, under that the object is valid
+        """
+        pass
+
+    def get(self, name: str, scope: str) -> None:
+        """
+        Abstract method to get an object from the data store
+        :param name: Name to look for
+        :param scope: scope to search the name for
+        :return: the stored object
+        """
+        pass
+
+    def search_name(self, name: str) -> None:
+        """
+        Abstract method to search for all occurrences of given `name` in the entire data store.
+        :param name: Name to look for
+        :return: search result
+        """
+        pass
+
+    def search_scope(self, scope: str) -> None:
+        """
+        Abstract method to search for all object names that are stored for given scope
+        :param scope: scope to look for
+        :return: search result
+        """
+        pass
+
+    def list_all_scopes(self) -> None:
+        """
+        Abstract method to list all scopes in data store
+        :return: all found scopes
+        """
+        pass
+
+
+class DataStoreByVariable(AbstractDataStore):
+
+    """
+    Data store for all settings for the experiment workflow to save experiment parameters for the proceeding modules
+    and predefine parameters loaded during the experiment setup phase. The data store is hierarchically structured, so
+    that global settings can be overwritten by local adjustments.
+
+    This implementation stores data as
+    <variable1>
+        <scope1>: value
+        <scope2>: value
+    <variable2>
+        <scope1>: value
+        <scope3>: value
+    """
+
+    def put(self, name: str, obj: Any, scope: str) -> None:
+        """
+        Store an object `obj` with given `name` under `scope`. In the current implementation, existing entries are
+        overwritten.
+        :param name: Name of object to store
+        :param obj: The object itself to be stored
+        :param scope: the scope / context of the object, under that the object is valid
+        """
+        # open new variable related store with `name` as key if not existing
+        if name not in self._store.keys():
+            self._store[name] = {}
+        self._store[name][scope] = obj
+
+    def get(self, name: str, scope: str) -> Any:
+        """
+        Retrieve an object with `name` from `scope`. If no object can be found in the exact scope, take an iterative
+        look on the levels above. Raises a NameNotFoundInDataStore error, if no object with given name can be found in
+        the entire data store. Raises a NameNotFoundInScope error, if the object is in the data store but not in the
+        given scope and its levels above (could be either included in another scope or a more detailed sub-scope).
+        :param name: Name to look for
+        :param scope: scope to search the name for
+        :return: the stored object
+        """
+        return self._stride_through_scopes(name, scope)[2]
+
+    def _stride_through_scopes(self, name, scope, depth=0):
+        if depth <= scope.count("."):
+            local_scope = scope.rsplit(".", maxsplit=depth)[0]
+            try:
+                return name, local_scope, self._store[name][local_scope]
+            except KeyError:
+                return self._stride_through_scopes(name, scope, depth + 1)
+        else:
+            occurrences = self.search_name(name)
+            if len(occurrences) == 0:
+                raise NameNotFoundInDataStore(f"Couldn't find {name} in data store")
+            else:
+                raise NameNotFoundInScope(f"Couldn't find {name} in scope {scope} . {name} is only defined in "
+                                          f"{occurrences}")
+
+    def search_name(self, name: str) -> List[str]:
+        """
+        Search for all occurrences of given `name` in the entire data store.
+        :param name: Name to look for
+        :return: list with all scopes and sub-scopes containing an object stored as `name`
+        """
+        return sorted(self._store[name] if name in self._store.keys() else [])
+
+    def search_scope(self, scope: str, current_scope_only=True, return_all=False) -> List[str or Tuple]:
+        """
+        Search for given `scope` and list all object names stored under this scope. To look also for all superior scopes
+        set `current_scope_only=False`. To return the scope and the object's value too, set `return_all=True`.
+        :param scope: scope to look for
+        :param current_scope_only: look only for all names for given scope if true, else search for names from superior
+            scopes too.
+        :param return_all: return name, definition scope and value if True, else just the name
+        :return: list with all object names (if `return_all=False`) or list with tuple of object name, object scope and
+            object value ordered by name (if `return_all=True`)
+        """
+        if current_scope_only:
+            names = []
+            for (k, v) in self._store.items():
+                if scope in v.keys():
+                    names.append(k)
+            if len(names) > 0:
+                if return_all:
+                    return sorted([(name, scope, self._store[name][scope]) for name in names], key=lambda tup: tup[0])
+                else:
+                    return sorted(names)
+            else:
+                raise EmptyScope(f"Given scope {scope} is not part of the data store. Available scopes are: "
+                                 f"{self.list_all_scopes()}")
+        else:
+            results = []
+            for name in self.list_all_names():
+                try:
+                    res = self._stride_through_scopes(name, scope)
+                    if return_all:
+                        results.append(res)
+                    else:
+                        results.append(res[0])
+                except (NameNotFoundInDataStore, NameNotFoundInScope):
+                    pass
+            if return_all:
+                return sorted(results, key=lambda tup: tup[0])
+            else:
+                return sorted(results)
+
+    def list_all_scopes(self) -> List[str]:
+        """
+        List all available scopes in data store
+        :return: names of all stored objects
+        """
+        scopes = []
+        for v in self._store.values():
+            for scope in v.keys():
+                if scope not in scopes:
+                    scopes.append(scope)
+        return sorted(scopes)
+
+    def list_all_names(self) -> List[str]:
+        """
+        List all names available in the data store.
+        :return: all names
+        """
+        return sorted(self._store.keys())
+
+
+class DataStoreByScope(AbstractDataStore):
+
+    """
+    Data store for all settings for the experiment workflow to save experiment parameters for the proceeding modules
+    and predefine parameters loaded during the experiment setup phase. The data store is hierarchically structured, so
+    that global settings can be overwritten by local adjustments.
+
+    This implementation stores data as
+    <scope1>
+        <variable1>: value
+        <variable2>: value
+    <scope2>
+        <variable1>: value
+        <variable3>: value
+    """
+
+    def put(self, name: str, obj: Any, scope: str) -> None:
+        """
+        Store an object `obj` with given `name` under `scope`. In the current implementation, existing entries are
+        overwritten.
+        :param name: Name of object to store
+        :param obj: The object itself to be stored
+        :param scope: the scope / context of the object, under that the object is valid
+        """
+        if scope not in self._store.keys():
+            self._store[scope] = {}
+        self._store[scope][name] = obj
+
+    def get(self, name: str, scope: str) -> Any:
+        """
+        Retrieve an object with `name` from `scope`. If no object can be found in the exact scope, take an iterative
+        look on the levels above. Raises a NameNotFoundInDataStore error, if no object with given name can be found in
+        the entire data store. Raises a NameNotFoundInScope error, if the object is in the data store but not in the
+        given scope and its levels above (could be either included in another scope or a more detailed sub-scope).
+        :param name: Name to look for
+        :param scope: scope to search the name for
+        :return: the stored object
+        """
+        return self._stride_through_scopes(name, scope)[2]
+
+    def _stride_through_scopes(self, name, scope, depth=0):
+        if depth <= scope.count("."):
+            local_scope = scope.rsplit(".", maxsplit=depth)[0]
+            try:
+                return name, local_scope, self._store[local_scope][name]
+            except KeyError:
+                return self._stride_through_scopes(name, scope, depth + 1)
+        else:
+            occurrences = self.search_name(name)
+            if len(occurrences) == 0:
+                raise NameNotFoundInDataStore(f"Couldn't find {name} in data store")
+            else:
+                raise NameNotFoundInScope(f"Couldn't find {name} in scope {scope} . {name} is only defined in "
+                                          f"{occurrences}")
+
+    def search_name(self, name: str) -> List[str]:
+        """
+        Search for all occurrences of given `name` in the entire data store.
+        :param name: Name to look for
+        :return: list with all scopes and sub-scopes containing an object stored as `name`
+        """
+        keys = []
+        for (key, val) in self._store.items():
+            if name in val.keys():
+                keys.append(key)
+        return sorted(keys)
+
+    def search_scope(self, scope: str, current_scope_only: bool = True, return_all: bool = False) -> List[str or Tuple]:
+        """
+        Search for given `scope` and list all object names stored under this scope. To look also for all superior scopes
+        set `current_scope_only=False`. To return the scope and the object's value too, set `return_all=True`.
+        :param scope: scope to look for
+        :param current_scope_only: look only for all names for given scope if true, else search for names from superior
+            scopes too.
+        :param return_all: return name, definition scope and value if True, else just the name
+        :return: list with all object names (if `return_all=False`) or list with tuple of object name, object scope and
+            object value ordered by name (if `return_all=True`)
+        """
+        if current_scope_only:
+            try:
+                if return_all:
+                    return [(name, scope, self._store[scope][name]) for name in sorted(self._store[scope].keys())]
+                else:
+                    return sorted(self._store[scope].keys())
+            except KeyError:
+                raise EmptyScope(f"Given scope {scope} is not part of the data store. Available scopes are: "
+                                 f"{self.list_all_scopes()}")
+        else:
+            results = []
+            for name in self.list_all_names():
+                try:
+                    res = self._stride_through_scopes(name, scope)
+                    if return_all:
+                        results.append(res)
+                    else:
+                        results.append(res[0])
+                except (NameNotFoundInDataStore, NameNotFoundInScope):
+                    pass
+            if return_all:
+                return sorted(results, key=lambda tup: tup[0])
+            else:
+                return sorted(results)
+
+    def list_all_scopes(self) -> List[str]:
+        """
+        List all available scopes in data store
+        :return: names of all stored objects
+        """
+        return sorted(self._store.keys())
+
+    def list_all_names(self) -> List[str]:
+        """
+        List all names available in the data store.
+        :return: all names
+        """
+        names = []
+        scopes = self.list_all_scopes()
+        for scope in scopes:
+            for name in self._store[scope].keys():
+                if name not in names:
+                    names.append(name)
+        return sorted(names)
+
+
diff --git a/test/test_datastore.py b/test/test_datastore.py
new file mode 100644
index 0000000000000000000000000000000000000000..5ba76bf8fc9c21553723cba3b2125be2d758e23b
--- /dev/null
+++ b/test/test_datastore.py
@@ -0,0 +1,222 @@
+__author__ = 'Lukas Leufen'
+__date__ = '2019-11-22'
+
+
+from src.datastore import AbstractDataStore, DataStoreByVariable, DataStoreByScope
+from src.datastore import NameNotFoundInDataStore, NameNotFoundInScope, EmptyScope
+import pytest
+
+
+class TestAbstractDataStore:
+
+    @pytest.fixture
+    def ds(self):
+        return AbstractDataStore()
+
+    def test_init(self, ds):
+        assert ds._store == {}
+
+
+class TestDataStoreByVariable:
+
+    @pytest.fixture
+    def ds(self):
+        return DataStoreByVariable()
+
+    def test_put(self, ds):
+        ds.put("number", 3, "general.subscope")
+        assert ds._store["number"]["general.subscope"] == 3
+
+    def test_get(self, ds):
+        ds.put("number", 3, "general.subscope")
+        assert ds.get("number", "general.subscope") == 3
+
+    def test_get_with_sub_scope(self, ds):
+        ds.put("number", 3, "general")
+        ds.put("number", 10, "general.subscope")
+        assert ds.get("number", "general.subscope") == 10
+        assert ds.get("number", "general") == 3
+
+    def test_get_with_not_existing_sub_scope(self, ds):
+        ds.put("number", 3, "general")
+        ds.put("number2", 10, "general.subscope")
+        ds.put("number2", 1, "general")
+        assert ds.get("number", "general.subscope") == 3
+
+    def test_raise_not_in_data_store(self, ds):
+        ds.put("number", 22, "general")
+        with pytest.raises(NameNotFoundInDataStore) as e:
+            ds.get("number3", "general")
+        assert "Couldn't find number3 in data store" in e.value.args[0]
+
+    def test_search(self, ds):
+        ds.put("number", 22, "general")
+        ds.put("number", 22, "general2")
+        ds.put("number", 22, "general.sub")
+        assert ds.search_name("number") == ["general", "general.sub", "general2"]
+
+    def test_raise_not_in_scope(self, ds):
+        ds.put("number", 11, "general.sub")
+        with pytest.raises(NameNotFoundInScope) as e:
+            ds.get("number", "general.sub2")
+        assert "Couldn't find number in scope general.sub2 . number is only defined in ['general.sub']" in e.value.args[0]
+
+    def test_list_all_scopes(self, ds):
+        ds.put("number", 22, "general2")
+        ds.put("number", 11, "general.sub")
+        ds.put("number2", 2, "general.sub")
+        ds.put("number", 3, "general.sub3")
+        ds.put("number", 1, "general")
+        assert ds.list_all_scopes() == ['general', 'general.sub', 'general.sub3', 'general2']
+
+    def test_search_scope(self, ds):
+        ds.put("number", 22, "general")
+        ds.put("number", 11, "general.sub")
+        ds.put("number1", 22, "general.sub")
+        ds.put("number2", 3, "general.sub.sub")
+        assert ds.search_scope("general.sub") == ["number", "number1"]
+
+    def test_search_empty_scope(self, ds):
+        ds.put("number", 22, "general2")
+        ds.put("number", 11, "general.sub")
+        with pytest.raises(EmptyScope) as e:
+            ds.search_scope("general.sub2")
+        assert "Given scope general.sub2 is not part of the data store." in e.value.args[0]
+        assert "Available scopes are: ['general.sub', 'general2']" in e.value.args[0]
+
+    def test_list_all_names(self, ds):
+        ds.put("number", 22, "general")
+        ds.put("number", 11, "general.sub")
+        ds.put("number1", 22, "general.sub")
+        ds.put("number2", 3, "general.sub.sub")
+        assert ds.list_all_names() == ["number", "number1", "number2"]
+
+    def test_search_scope_and_all_superiors(self, ds):
+        ds.put("number", 22, "general")
+        ds.put("number", 11, "general.sub")
+        ds.put("number1", 22, "general.sub")
+        ds.put("number2", 3, "general.sub.sub")
+        assert ds.search_scope("general.sub", current_scope_only=False) == ["number", "number1"]
+        assert ds.search_scope("general.sub.sub", current_scope_only=False) == ["number", "number1", "number2"]
+
+    def test_search_scope_return_all(self, ds):
+        ds.put("number", 22, "general")
+        ds.put("number", 11, "general.sub")
+        ds.put("number1", 22, "general.sub")
+        ds.put("number2", 3, "general.sub.sub")
+        assert ds.search_scope("general.sub", return_all=True) == [("number", "general.sub", 11),
+                                                                   ("number1", "general.sub", 22)]
+
+    def test_search_scope_and_all_superiors_return_all(self, ds):
+        ds.put("number", 22, "general")
+        ds.put("number", 11, "general.sub")
+        ds.put("number1", 22, "general.sub")
+        ds.put("number2", 3, "general.sub.sub")
+        ds.put("number", "ABC", "general.sub.sub")
+        assert ds.search_scope("general.sub", current_scope_only=False, return_all=True) == \
+            [("number", "general.sub", 11), ("number1", "general.sub", 22)]
+        assert ds.search_scope("general.sub.sub", current_scope_only=False, return_all=True) == \
+            [("number", "general.sub.sub", "ABC"), ("number1", "general.sub", 22), ("number2", "general.sub.sub", 3)]
+
+
+class TestDataStoreByScope:
+
+    @pytest.fixture
+    def ds(self):
+        return DataStoreByScope()
+
+    def test_put_with_scope(self, ds):
+        ds.put("number", 3, "general.subscope")
+        assert ds._store["general.subscope"]["number"] == 3
+
+    def test_get(self, ds):
+        ds.put("number", 3, "general.subscope")
+        assert ds.get("number", "general.subscope") == 3
+
+    def test_get_with_sub_scope(self, ds):
+        ds.put("number", 3, "general")
+        ds.put("number", 10, "general.subscope")
+        assert ds.get("number", "general.subscope") == 10
+        assert ds.get("number", "general") == 3
+
+    def test_get_with_not_existing_sub_scope(self, ds):
+        ds.put("number", 3, "general")
+        ds.put("number2", 10, "general.subscope")
+        ds.put("number2", 1, "general")
+        assert ds.get("number", "general.subscope") == 3
+
+    def test_raise_not_in_data_store(self, ds):
+        ds.put("number", 22, "general")
+        with pytest.raises(NameNotFoundInDataStore) as e:
+            ds.get("number3", "general")
+        assert "Couldn't find number3 in data store" in e.value.args[0]
+
+    def test_search(self, ds):
+        ds.put("number", 22, "general")
+        ds.put("number", 22, "general2")
+        ds.put("number", 22, "general.sub")
+        assert ds.search_name("number") == ["general", "general.sub", "general2"]
+
+    def test_raise_not_in_scope(self, ds):
+        ds.put("number", 11, "general.sub")
+        with pytest.raises(NameNotFoundInScope) as e:
+            ds.get("number", "general.sub2")
+        assert "Couldn't find number in scope general.sub2 . number is only defined in ['general.sub']" in e.value.args[0]
+
+    def test_list_all_scopes(self, ds):
+        ds.put("number", 22, "general2")
+        ds.put("number", 11, "general.sub")
+        ds.put("number2", 2, "general.sub")
+        ds.put("number", 3, "general.sub3")
+        ds.put("number", 1, "general")
+        assert ds.list_all_scopes() == ['general', 'general.sub', 'general.sub3', 'general2']
+
+    def test_search_scope(self, ds):
+        ds.put("number", 22, "general")
+        ds.put("number", 11, "general.sub")
+        ds.put("number1", 22, "general.sub")
+        ds.put("number2", 3, "general.sub.sub")
+        assert ds.search_scope("general.sub") == ["number", "number1"]
+
+    def test_search_empty_scope(self, ds):
+        ds.put("number", 22, "general2")
+        ds.put("number", 11, "general.sub")
+        with pytest.raises(EmptyScope) as e:
+            ds.search_scope("general.sub2")
+        assert "Given scope general.sub2 is not part of the data store." in e.value.args[0]
+        assert "Available scopes are: ['general.sub', 'general2']" in e.value.args[0]
+
+    def test_list_all_names(self, ds):
+        ds.put("number", 22, "general")
+        ds.put("number", 11, "general.sub")
+        ds.put("number1", 22, "general.sub")
+        ds.put("number2", 3, "general.sub.sub")
+        assert ds.list_all_names() == ["number", "number1", "number2"]
+
+    def test_search_scope_and_all_superiors(self, ds):
+        ds.put("number", 22, "general")
+        ds.put("number", 11, "general.sub")
+        ds.put("number1", 22, "general.sub")
+        ds.put("number2", 3, "general.sub.sub")
+        assert ds.search_scope("general.sub", current_scope_only=False) == ["number", "number1"]
+        assert ds.search_scope("general.sub.sub", current_scope_only=False) == ["number", "number1", "number2"]
+
+    def test_search_scope_return_all(self, ds):
+        ds.put("number", 22, "general")
+        ds.put("number", 11, "general.sub")
+        ds.put("number1", 22, "general.sub")
+        ds.put("number2", 3, "general.sub.sub")
+        assert ds.search_scope("general.sub", return_all=True) == [("number", "general.sub", 11),
+                                                                   ("number1", "general.sub", 22)]
+
+    def test_search_scope_and_all_superiors_return_all(self, ds):
+        ds.put("number", 22, "general")
+        ds.put("number", 11, "general.sub")
+        ds.put("number1", 22, "general.sub")
+        ds.put("number2", 3, "general.sub.sub")
+        ds.put("number", "ABC", "general.sub.sub")
+        assert ds.search_scope("general.sub", current_scope_only=False, return_all=True) == \
+            [("number", "general.sub", 11), ("number1", "general.sub", 22)]
+        assert ds.search_scope("general.sub.sub", current_scope_only=False, return_all=True) == \
+            [("number", "general.sub.sub", "ABC"), ("number1", "general.sub", 22), ("number2", "general.sub.sub", 3)]
+