Я бы сказал, что различие между данными, кодом и конфигурацией должно быть сделано в контексте определенного компонента. Иногда это очевидно, иногда меньше.
Например, для компилятора исходный код, который он потребляет, и объектный код, который он создает, являются данными - и должны быть отделены от собственного кода компилятора.
В вашем случае вы, похоже, описываете вариант особенно мощного файла конфигурации, который может содержать код. Так же, как, например, GIMP позволяет вам «настраивать» плагины, используя Scheme. Как разработчик компонента, который читает эту конфигурацию, вы будете думать о ней как о данных. При работе на другом уровне - при написании конфигурации - вы будете думать об этом как о коде.
Это очень мощный способ проектирования.
Применив это к основному вопросу («Как бы вы закодировали приведенный выше пример?»), Можно было бы выбрать или разработать высокоуровневый предметно-ориентированный язык (DSL) для определения правил. При запуске или при первой необходимости сервер считывает правило и выполняет его.
Предоставляет интерфейс администратора, позволяющий администратору
- протестировать новый файл правил
- заменить текущую конфигурацию конфигурацией из нового файла правил
... все это произойдет во время выполнения.
DSL может быть чем-то таким же простым, как анализатор таблиц или синтаксический анализатор XML, или может быть чем-то более сложным, чем язык сценариев. Из C легко встроить Python или Lua. Из Java легко встроить Groovy или Clojure.
Вы можете включить скомпилированный код во время выполнения, используя хитрые ссылки или трюки с загрузчиком классов. На мой взгляд, это кажется более сложным и менее ценным, чем встроенная опция DSL.