Skip to content

Instantly share code, notes, and snippets.

@mindplay-dk
Created February 11, 2015 17:00
Show Gist options
  • Save mindplay-dk/aaa4f4b747d09729c84a to your computer and use it in GitHub Desktop.
Save mindplay-dk/aaa4f4b747d09729c84a to your computer and use it in GitHub Desktop.
Approaches to modeling

Approaches to modeling

In this short write-up, I will briefly summarize the types of modeling I have seen and attempted over the years, highlighting some of the advantages and drawbacks of each, ending with a conclusion explaining which one I prefer and why.

1. Bare Models

Bare models without any run-time annotations, e.g.:

class User
{
    /** @var string */
    public $email;
}

Controllers and views need to be fully hard-coded and tailored to specific input-types, etc. - this is the simplest approach, but not the most elegant.

2. Bare Models plus type-objects

Every model has a type-object with synchronous property-names providing meta information about the properties, e.g.:

class User
{
    /** @var string */
    public $email;
}

class UserType
{
    /** @var EmailInput */
    public $email;
    
    public function __construct()
    {
        $this->email = new EmailInput('email');
        $this->email->required = true;
        $this->email->unique = true;
    }
}

This requires more up-front model work, but the pay-off is the ability to reuse information provided by the type-object, e.g. for cross-cutting concerns dependent on the same information - for example, the $unique property could be used to drive both validation and schema generation, etc.

It requires no framework, however, so it is still simple.

3. Annotated Models

Meta-data is embedded inside the source-code in the form of annotations, e.g.:

class User
{
    /**
     * @var string
     * @input(type="text")
     */
    public $first_name;
}

This approach appears on the surface to be simpler than the type-objects approach. However, parsing annotations requires a complex framework that performs parsing, validation, caching, etc. and is actually far from simple.

4. Meta-modeling with model code-generation

This involves building a run-time meta-model driving a code-generation framework, and could look something like this:

$class = new MetaClass('User');
$class->addProperty(new EmailProperty('email'));

$code_generator->run($class);

The code generator would emit a model, a meta-model, and possibly other things like factory-classes etc.

Assuming passive (design-time) code-generation, as opposed to active (run-time) code-generation, this does provide static coupling at design-time, but perhaps not to the full extend - for example, refactorings (such as changing a property name) would involve a code-generation step, so even though you have static coupling, you can't really have automated refactorings.

It goes without saying, this approach is by no means simple - it involves building a large complicated code-generation engine, a lot of careful design, optimizations, etc.

5. Meta-modeling from a specification

This is the approach used by e.g. the M# Language, in which a declarative specification is written in a custom language, consumed by a parser and used to drive a code-generator.

The premise of this is brevity, but obviously this comes at the cost of very high complexity, since a custom language requires custom IDE support, as well as all of the complexity and drawbacks of other code-generation approaches.

6. Code-generation from type-objects

Similar to approach number 2 ("Bare Models plus type-objects") but with the actual model being generated by letting a code-generator consume the type-object, just like any other service.

In some ways, this approach is both the best and the worst of all worlds - you get to describe and compose the model by implementing your type-objects using all the features of the host language, you avoid having to repeat anything, but it comes with the same problems (e.g. refactoring of the generated model) and at least some of the complexity of the other code-generation systems.

Conclusion

My favorite is number 2: Bare Models plus type-objects, because:

  1. Avoids code-generation.

  2. Static coupling and automated refactoring everywhere.

  3. The non-static property-name as a string is encapsulated once in the type-object and never needs to be repeated in an unsafe manner.

The bare models plus type objects approach keeps the overall complexity low, keeps transparency high, avoids introduction of design-time steps and completely obviates the need for any framework at the model-level - your models, and type-objects, are just code.

Granted, developers will need to learn and understand why every model is implemented as two classes, but this is a small price to pay - and it inspires learning and independent thinking.

@mindplay-dk
Copy link
Author

I should write more about type-objects, which almost nobody is doing - it has been by far the most productive and satisfying approach I've used...

@ralphschindler
Copy link

I think you want to have a look at the patterns and diction used by Domain Driven Design, specifically: repositories, entities (with "identity"), value objects, aggregates and factories. For the cliffs notes, check out "Domain Driven Design Quickly"; if you google for that, a PDF should come up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment