E11022: class has component characteristics - use 'defines component'
What went wrong
Your class has 4+ service (abstract/trait) fields and 0 data fields. This is a pure service coordinator - architecturally, it's a component.
EK9 has distinct constructs for distinct architectural roles:
- Classes: Data + behavior (state + methods operating on that state)
- Components: Service coordination with dependency injection
A class with only service dependencies and no data is acting as a component - it coordinates other services rather than managing state.
Type classification
SERVICE fields (behavior contracts - you program to the interface):
- Traits: 'validator as Validator?' where Validator is a trait
- Abstract classes: 'processor as Processor?' where Processor is abstract
- Abstract functions: 'handler as Handler?' where Handler is a function signature
DATA fields (concrete values - you have the actual implementation):
- Records: 'order as OrderRecord?'
- Text constructs: 'messages as Translations?'
- Concrete classes: 'name as String?', 'count as Integer?', 'config as MyConfig?'
THE KEY DISTINCTION - Programming to interface vs instantiating concrete: a field typed as a trait ('validator as Validator?' where Validator is a trait) is a SERVICE; a field typed as a concrete class and given an instance of it ('productivitySystem' declared as a ProductivitySystem and initialised with 'ProductivitySystem()') is DATA.
The second pattern creates and holds a concrete object. Even though ProductivitySystem might have methods, it's a concrete class you instantiate directly - making it DATA, not a service abstraction.
See: https://ek9.io/composition.html The "EmployeeDatabase" example holds "productivitySystem", "payrollSystem", and "addresses" - all concrete class instances (DATA fields). This is valid composition where concrete helpers are used, not service coordination.
Parameterized types - based on what's INSIDE:
- 'validators as List of Validator?' -> SERVICE (Validator is a trait)
- 'names as List of String?' -> DATA (String is concrete)
- 'users as Dict of (String, UserRecord)?' -> DATA (value type is concrete)
EXCLUSION: Generic classes are excluded from this check. Generic classes cannot be components in EK9, so this error doesn't apply to them.
Worked examples
Compilable examples demonstrating this error and the correct fix:
- ek9 -q 856 (When should I use a component instead of a class in EK9?)
How to fix it
- CONVERT TO COMPONENT (recommended for application code)
- Declare it in a component block instead of a class block
- Mark fields for injection with '!' suffix
- Register in your application's component section
- ADD A DATA FIELD (if component is inappropriate)
- Adding even one data field removes this error
- Use for: library code, test utilities, or intentional facades
- REDUCE SERVICE COUNT (if over-designed)
- Combine related services into a single service
- Question whether all 4+ services are truly needed
Transform
The [EXAMPLE] section below shows a class holding four trait-typed services and no data, and the coordinator declared as a component instead. Converted to a component, its service fields can be injected ('serviceA as ServiceA!') rather than constructed. The other way out is a class that gains a real data field (a 'config' it holds, say), which makes it a legitimate class.
When to keep as class
- Library code that shouldn't depend on Application registration
- Test utilities and mocks
- Intentional facade pattern with legitimate reason
Why this matters
- Components enable EK9's dependency injection system
- Components can be registered and wired automatically
- Using the correct construct makes architectural intent clear
- EK9's compile-time verification is more effective with proper typing
THRESHOLD: >= 4 service fields AND 0 data fields
Research basis
- Martin, R.C.: Single Responsibility Principle - "A class should have one reason to change"
- Riel, A.J.: 'OO Design Heuristics' (1996) - "Top-level classes share work uniformly"
- Marinescu, R.: IEEE ICSM 2004 - Detection strategies for design flaws
- Brown et al.: 'AntiPatterns' (1998) - The Blob pattern definition
Examples
Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.
Fails with E11022: class has component characteristics - use 'defines component'
#!ek9 defines module error.e11022.bad defines trait ServiceA doA() as abstract ServiceB doB() as abstract ServiceC doC() as abstract ServiceD doD() as abstract defines class //Concrete implementations of the traits ServiceAImpl with trait of ServiceA override doA() require true ServiceBImpl with trait of ServiceB override doB() require true ServiceCImpl with trait of ServiceC override doC() require true ServiceDImpl with trait of ServiceD override doD() require true defines class PureCoordinator //4 service (trait) fields initialized to implementations //0 data fields = should be component serviceA as ServiceA: ServiceAImpl() serviceB as ServiceB: ServiceBImpl() serviceC as ServiceC: ServiceCImpl() serviceD as ServiceD: ServiceDImpl() orchestrate() serviceA.doA() serviceB.doB() serviceC.doC() serviceD.doD() default operator ?
Fixed: A type that only coordinates services is a component
#!ek9 defines module error.e11022.good defines trait ServiceA doA() as abstract ServiceB doB() as abstract defines class ServiceAImpl with trait of ServiceA override doA() stdout <- Stdout() stdout.println("A") ServiceBImpl with trait of ServiceB override doB() stdout <- Stdout() stdout.println("B") defines component Coordinator serviceA as ServiceA: ServiceAImpl() serviceB as ServiceB: ServiceBImpl() orchestrate() serviceA.doA() serviceB.doB() default operator ?
Questions and answers
These Q&A entries show E11022 and its fix: