Patterns of Enterprise Application Architecture

Category: Hardware & DIY
Author: Martin Fowler
4.2
All Stack Overflow 137
This Year Stack Overflow 2
This Month Stack Overflow 14

Comments

by auspex   2022-04-08
Fowler is a great resource for this:

https://martinfowler.com/articles/enterprisePatterns.html

More specifically this book: https://www.amazon.com/Patterns-Enterprise-Application-Archi...

by James Prickett   2019-07-21

if you are doing enterprise softare:

http://www.amazon.com/Enterprise-Application-Architecture-Addison-Wesley-Signature/dp/0321127420/ref=sr_1_1?ie=UTF8&s=books&qid=1247698486&sr=1-1

by anonymous   2019-07-21

Imagine a situation where...

There's a name for that, it's long transaction, and if you think you need to implement it, that's a sign that it may be time to re-think your design.

Why it's bad, and how to avoid it is a book-level topic.

Here's one book that covers it pretty well:

https://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420

by anonymous   2019-07-21

This may help you define the vision. Practical API Design This will help define object relational mapping, the one and only classic Patterns of Enterprise Application Architecture. Specifically chapters 3,4,5.

Also worth studying how other folk have done it. Stand on the shoulders of giants so to speak. Doctorine ORM and Doctorine

Also for the ORM side, read the ORM bible. Information Modeling and Relational Databases:From Conceptual Analysis to Logical Design

by anonymous   2019-07-21

Actually the current version is quite ok (if considered with no context) because it makes for a cleaner code, since it closer matches principle of least astonishment. Technically, you could rewrite it as this (but that would actually make it worse code):

abstract class Employee {
    public function getAlias() {
        return $this->alias;
    }
}

class Manager extends Employee {
    protected $alias = 'mngr';
}


$user = new Manager;
echo $user->getAlias();

Live code: https://3v4l.org/sjVOT

The more important aspect is the purpose of this code. You mentioned, that you would want to use something like this for dealing with single-table inheritance, but here is the important part:

Your domain entities should not be aware of how your persistence layer works.

And pulling structural information from the domain layer for use in some query-builder is a terrible idea. I would recommend for you to instead looks at data mapper pattern (you probably should actually read the PoEAA book).

Your domain entities should not know any details about how (or even "if") they is being saved or restored.

by anonymous   2019-07-21

Read Martin Fowler's Patterns of Enterprise Application Architecture:

http://www.amazon.co.uk/Enterprise-Application-Architecture-Addison-Wesley-Signature/dp/0321127420

And also Architecting Applications for the Enterprise:

http://www.amazon.co.uk/Microsoft-NET-Architecting-Applications-PRO-Developer/dp/073562609X

Both are good. The latter has some .NET examples of patterns exposed in Martin Fowler's book. Both books explain when is good to use a given pattern.

by anonymous   2019-07-21

A couple of recommended books:

  • Patterns of Enterprise Application Architecture
  • Enterprise Integration Patterns
by scarface74   2019-01-03
(None of these are affiliate links)

https://www.amazon.com/Domain-Driven-Design-Tackling-Complex...

https://www.amazon.com/Clean-Code-Handbook-Software-Craftsma...

https://www.amazon.com/Clean-Architecture-Craftsmans-Softwar...

https://www.amazon.com/Patterns-Enterprise-Application-Archi...

https://www.amazon.com/Refactoring-Improving-Existing-Addiso...

https://www.amazon.com/Code-Complete-Practical-Handbook-Cons...

https://www.amazon.com/Pragmatic-Programmer-Journeyman-Maste...

https://www.amazon.com/Mythical-Man-Month-Software-Engineeri...

And just because it’s asked at every interview.

https://www.amazon.com/Design-Patterns-Object-Oriented-Addis...

I’m focused on AWS these days, but a lot of these principals are universal.

https://d1.awsstatic.com/whitepapers/architecture/AWS_Well-A...

by anonymous   2018-03-19

Warning:
The information in this posts is extremely outdated. It represents my understanding of MVC pattern as it was more then 2 years ago. It will be updated when I get round to it. Probably this month (2013.09).

Damn it! (2017.11).

Model itself should not contain any SQL. Ever. It is meant to only contain domain business logic.

The approach i would recommend is to separate the responsibilities, which are not strictly "business logic" into two other other sets of constructs : Domain Objects and Data Mappers.

For example, if you are making a blog, then the Model will not be Post. Instead most likely the model will be Blog , and this model will deal with multiple Domain Objects: multiple instances of Post, Comment, User and maybe other objects.

In your model, the domain objects should not know how to store themselves in database. Or even be aware of the existence of any form of storage. That is a responsibility of Data Mappers. All you should do in the Model is to call $mapper->store( $comment );. And the data mapper should know how to store one specific type of domain objects, and win which table to put the information ( usually the storage of of single domain object actually affects multiple tables ).


Some code

(only relevant fragments from files):

  • I assume that you know how to write a good constructor .. if you have doubts, read this article
  • nothing is namespaced in example, but it should be
  • anything that begins with _ in example is protected

from /application/bootstrap.php

/* --- snip --- */

$connection = new PDO( 'sqlite::memory:' );
$model_factory = new ModelFactory( $connection );

$controller = new SomeController( $request , $model_factory );

/* --- snip --- */

$controller->{$action}();

/* --- snip --- */
  • controller does not need to be aware of database connection.
  • if you want to change DB connection for whole application, you need to change single line
  • to change the way how Model's are made, you create different class which implements same interface as ModelFactory

from /framework/classes/ModelFactory.php

/* --- snip --- */

class ModelFactory implements ModelBuilderInterface
{
   /* --- snip --- */

   protected function _prepare()
   {
      if ( $this->_object_factory === null  )
      {
         $this->_object_factory = new DomainObjectFactory;
      }
      if ( $this->_mapper_factory === null )
      {
         $this->_mapper_factory = new DataMapperFactory( $this->_connection );
      }
   }

   public function build( $name )
   {
      $this->_prepare();
      return new {$name}( $this->_object_mapper , $this->_data_mapper );
   }

   /* --- snip --- */

}
  • only data mappers will use database , only mapper factory need connection
  • all the dependencies of Model are injected in constructor
  • every DataMapper instance in the application uses same DB connection, no Global State (video) required.

file /application/controllers/SomeController.php

/* --- snip --- */

   public function get_foobar()
   {
      $factory = $this->_model_factory;
      $view = $this->_view;

      $foo = $factory->build( 'FooModel' );
      $bar = $factory->build( 'BarModel' );

      $bar->set_language( $this->_request->get('lang') );

      $view->bind( 'ergo' , $foo );

      /* --- snip --- */

   }

/* --- snip --- */
  • controller is unaware of model creation details
  • controller is only responsible for wiring and changing the state of elements

file /application/models/FooModel.php

/* --- snip --- */

   public function find_something( $param  , $filter )
   {
      $something = $this->_object_factory('FooBar');
      $mapper = $this->_mapper_factory('FooMapper');

      $something->set_type( $param );
      $mapper->use_filter( $filter )->fetch( $something );

      return $something;
   }

/* --- snip --- */
  • domain object is responsible for validating the given parameters
  • view receives and decides how to present it
  • mapper takes the object and puts in it all the required information from storage ( it doesn't have to be DB .. it could be taken from some file, or an external REST API )

I hope this will help you understand the separation between DB logic and business logic ( and actually , presentation logic too )


Few notes

Model should never extend Database or ORM, because Model is not a subset of them. By extending a class, you are declaring that has all the characteristics of the superclass, but with minor exceptions.

class Duck extends Bird{}
class ForestDuck extends Duck{}
// this is ok

class Table extends Database{}
class Person extends Table{}
// this is kinda stupid and a bit insulting

Besides the obvious logic-issues, if your Model is tightly coupled with underlaying Database, it makes the code extremely hard to test (talking about Unit Testing (video)).


I personally think, that ORMs are useless and in large project - even harmful. Problem stems from the fact that ORMs are trying to bridge two completely different ways of approaching problems : OOP and SQL.

If you start project with ORM then, after short learning curve, you are able to write simple queries very fast. But by the time you start hitting the ORM's limitations and problems, you are already completely invested in the use of ORM ( maybe even new people were hired , who were really good at your chosen , but sucked at plain SQL ). You end up in situation where every new DB related issue take more and more time to solve. And if you have been using ORM based on ActiveRecord pattern, then the problems directly influence your Models.

Uncle Bob calls this "technical debt".


Few books

loosely related to subject

by anonymous   2017-11-06
Sounds like something beyond the scope of this site. Also, each of the "areas", that you mentioned, is a huge topic. I would start by reading [Clean Architecture](https://www.amazon.com/Clean-Architecture-Craftsmans-Software-Structure/dp/0134494164) and [PoEAA](https://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420) books before even tying to approach any pf those topics.
by anonymous   2017-08-20

This is a rather larger question than can easily be handled quickly, but aligning java classes to tables is not necessarily a good approach, precisely because as you note, some things naturally involve multiple tables in complex relations.

I'd recommend starting by looking at Martin Fowler's book Patterns of Enterprise Application Architecture.

Patterns of Enterprise Application Architecture.

There are also some notes on the patterns in this book at his website.

Your usage most nearly resembles Table Data Gateway. Following this pattern, it would be perfectly reasonable to have methods in each of your dbManager classes that retrieves data from its associated table but involves another table as part of a where clause.

You might also want to consider Object-relational mapping as implemented for instance by Hibernate.

by anonymous   2017-08-20

I think you should consider reading these books.

alt text

alt text http://img.infibeam.com/img/a3c437df/218/6/9780735626218.jpg

by anonymous   2017-08-20

Lets answer your questions from top to bottom, and see what I can add to what you say.

There will be one class per database table as explained below.

User - A class for interacting with the user table. The class contains functions such as createUser, updateUser, etc.

Locations - A class for interacting with the locations table. The class contains functions >such as searchLocation, createLocation, updateLocation, etc.

Essentially you have to choices here. The method you described is called the active record pattern. The object itself knows how and where it is stored. For simple objects that interact with a database to create / read / update / delete, this pattern is really usefull.

If the database operations become more extensive and less simple to understand, it is often a good choice to go with a data mapper (eg. this implementation). This is a second object that handles all the database interactions, while the object itself (eg. User or Location) only handles operations that are specific to that object (eg. login or goToLocation). If you ever want to chance the storage of your objects, you will only have to create a new data mapper. Your object won't even know that something changed in the implementation. This enforces encapsulation and seperation of concerns.

There are other options, but these two are the most used ways to implement database interactions.

In addition, I am thinking of creating another class as follows: -

DatabaseHelper : A class that will have a static member that represents the connection to the database. This class will contain the lower level methods for executing SQL queries such as executeQuery(query,parameters), executeUpdate(query,parameters) and so on.

What you are describing here sounds like a singleton. Normally this isn't really a good design choice. Are you really, really certain that there will never be a second database? Probably not, so you should not confine yourself to an implementation that only allowes for one database connection. Instead of making a DatabaseHelper with static members, you can better create a Database object with some methods that allow you to connect, disconnect, execute a query, etc. This way you can reuse it if you ever need a second connection.

At this point, I have two options to use the DatabaseHelper class in other classes : -

  1. The User and Locations class will extend the DatabaseHelper class so that they can use the inherited executeQuery and executeUpdate methods in DatabaseHelper. In this case, DatabaseHelper will ensure that there is only one instance of the connection to the database at any given time.
  2. The DatabaseHelper class will be injected in the User and Locations class through a Container class that will make User and Location instances. In this case, the Container will make sure that there is only one instance of DatabaseHelper in the application at any given time.

These are the two approaches that quickly come to my mind. I want to know which approach to go with. It is possible that both these approaches are not good enough, in which case, I want to know any other approach that I can go with to implement the database interaction module.

The first option isn't really viable. If you read the description of inheritance, you will see that inheritance is normally used to create a subtype of an existing object. An User is not a subtype of a DatabaseHelper, nor is a location. A MysqlDatabase would be a subtype of a Database, or a Admin would be a subtype of an User. I would advise against this option, as it isn't following the best practices of object oriented programming.

The second option is better. If you choose to use the active record method, you should indeed inject the Database into the User and Location objects. This should of course be done by some third object that handles all these kind of interactions. You will probably want to take a look at dependency injection and inversion of control.

Otherwise, if you choose the data mapper method, you should inject the Database into the data mapper. This way it is still possible to use several databases, while seperating all your concerns.

For more information about the active record pattern and the data mapper pattern, I would advise you to get the Patterns of Enterprise Application Architecture book of Martin Fowler. It is full of these kind of patterns and much, much more!

I hope this helps (and sorry if there are some really bad English sentences in there, I'm not a native speaker!).

== EDIT ==

Using the active record pattern of data mapper pattern also helps in testing your code (like Aurel said). If you seperate all peaces of code to do just one thing, it will be easier to check that it is really doing this one thing. By using PHPUnit (or some other testing framework) to check that your code is properly working, you can be pretty sure that no bugs will be present in each of your code units. If you mix up the concerns (like when you choose option 1 of your choices), this will be a whole lot harder. Things get pretty mixed up, and you will soon get a big bunch of spaghetti code.

== EDIT2 ==

An example of the active record pattern (that is pretty lazy, and not really active):

class Controller {
    public function main() {
        $database = new Database('host', 'username', 'password');
        $database->selectDatabase('database');

        $user = new User($database);
        $user->name = 'Test';

        $user->insert();

        $otherUser = new User($database, 5);
        $otherUser->delete();
    }
}

class Database {
    protected $connection = null;

    public function __construct($host, $username, $password) {
        // Connect to database and set $this->connection
    }

    public function selectDatabase($database) {
        // Set the database on the current connection
    }

    public function execute($query) {
        // Execute the given query
    }
}

class User {
    protected $database = null;

    protected $id = 0;
    protected $name = '';

    // Add database on creation and get the user with the given id
    public function __construct($database, $id = 0) {
        $this->database = $database;

        if ($id != 0) {
            $this->load($id);
        }
    }

    // Get the user with the given ID
    public function load($id) {
        $sql = 'SELECT * FROM users WHERE id = ' . $this->database->escape($id);
        $result = $this->database->execute($sql);

        $this->id = $result['id'];
        $this->name = $result['name'];
    }

    // Insert this user into the database
    public function insert() {
        $sql = 'INSERT INTO users (name) VALUES ("' . $this->database->escape($this->name) . '")';
        $this->database->execute($sql);
    }

    // Update this user
    public function update() {
        $sql = 'UPDATE users SET name = "' . $this->database->escape($this->name) . '" WHERE id = ' . $this->database->escape($this->id);
        $this->database->execute($sql);
    }

    // Delete this user
    public function delete() {
        $sql = 'DELETE FROM users WHERE id = ' . $this->database->escape($this->id);
        $this->database->execute($sql);
    }

    // Other method of this user
    public function login() {}
    public function logout() {}
}

And an example of the data mapper pattern:

class Controller {
    public function main() {
        $database = new Database('host', 'username', 'password');
        $database->selectDatabase('database');

        $userMapper = new UserMapper($database);

        $user = $userMapper->get(0);
        $user->name = 'Test';
        $userMapper->insert($user);

        $otherUser = UserMapper(5);
        $userMapper->delete($otherUser);
    }
}

class Database {
    protected $connection = null;

    public function __construct($host, $username, $password) {
        // Connect to database and set $this->connection
    }

    public function selectDatabase($database) {
        // Set the database on the current connection
    }

    public function execute($query) {
        // Execute the given query
    }
}

class UserMapper {
    protected $database = null;

    // Add database on creation
    public function __construct($database) {
        $this->database = $database;
    }

    // Get the user with the given ID
    public function get($id) {
        $user = new User();

        if ($id != 0) {
            $sql = 'SELECT * FROM users WHERE id = ' . $this->database->escape($id);
            $result = $this->database->execute($sql);

            $user->id = $result['id'];
            $user->name = $result['name'];
        }

        return $user;
    }

    // Insert the given user
    public function insert($user) {
        $sql = 'INSERT INTO users (name) VALUES ("' . $this->database->escape($user->name) . '")';
        $this->database->execute($sql);
    }

    // Update the given user
    public function update($user) {
        $sql = 'UPDATE users SET name = "' . $this->database->escape($user->name) . '" WHERE id = ' . $this->database->escape($user->id);
        $this->database->execute($sql);
    }

    // Delete the given user
    public function delete($user) {
        $sql = 'DELETE FROM users WHERE id = ' . $this->database->escape($user->id);
        $this->database->execute($sql);
    }
}

class User {
    public $id = 0;
    public $name = '';

    // Other method of this user
    public function login() {}
    public function logout() {}
}

== EDIT 3: after edit by bot ==

Note that the Container class will contain a static member of type DatabaseHelper. It will contain a private static getDatabaseHelper() function that will return an existing DatabaseHelper instance or create a new DatabaseHelper instance if one does not exists in which case, it will populate the connection object in DatabaseHelper. The Container will also contain static methods called makeUser and makeLocation that will inject the DatabaseHelper into User and Locations respectively.

After reading a few answers, I realize that the initial question has almost been answered. But there is still a doubt that needs to be clarified before I can accept the final answer which is as follows.

What to do when I have multiple databases to connect to rather than a single database. How does the DatabaseHelper class incorporate this and how does the container inject appropriate database dependencies in the User and Location objects?

I think there is no need for any static property, nor does the Container need those makeUser of makeLocation methods. Lets assume that you have some entry point of your application, in which you create a class that will control all flow in your application. You seem to call it a container, I prefer to call it a controller. After all, it controls what happens in your application.

$controller = new Controller();

The controller will have to know what database it has to load, and if there is one single database or multiple ones. For example, one database contains the user data, anonther database contains the location data. If the active record User from above and a similar Location class are given, then the controller might look as follows:

class Controller {
    protected $databases = array();

    public function __construct() {
        $this->database['first_db'] = new Database('first_host', 'first_username', 'first_password');
        $this->database['first_db']->selectDatabase('first_database');

        $this->database['second_db'] = new Database('second_host', 'second_username', 'second_password');
        $this->database['second_db']->selectDatabase('second_database');
    }

    public function showUserAndLocation() {
        $user = new User($this->databases['first_database'], 3);
        $location = $user->getLocation($this->databases['second_database']);

        echo 'User ' . $user->name . ' is at location ' . $location->name;
    }

    public function showLocation() {
        $location = new Location($this->database['second_database'], 5);

        echo 'The location ' . $location->name . ' is ' . $location->description;
    }
}

Probably it would be good to move all the echo's to a View class or something. If you have multiple controller classes, it might pay off to have a different entrypoint that creates all databases and pushes them in the controller. You could for example call this a front controller or an entry controller.

Does this answer you open questions?

by anonymous   2017-08-20

You main issue stems from fact that you have been ignoring one of core ideas in OOP: single responsibility principle .. then again, it seems like everyone who provided "answers" have no ideas what SRP is either.

What you refer to as "business logic" should be kept separate from storage related operation. Neither the instance of User no Admin should be aware oh how the storage is performed, because it is not business logic.

$entity = new Admin;
$mapper = new AdminMapper( $db );

$entity->setName('Wintermute');
$mapper->fetch( $entity ); 
//retrieve data for admin with that name

$entity->setName('Neuromancer');
$mapper->store( $entity );
// rename and save

What you see above is an extremely simplified application of data mapper pattern. The goal of this pattern is to separate business and storage logic.

If, instead of instantiating the mapper directly $mapper = new AdminMapper( $db ), it would be provided by an injected factory $mapper = $this->mapperFactory->build('Admin') (as it should be in proper codebase), you would have no indication about the storage medium. The data could be stored either in an SQL DB or file, or some remote REST API. If interface for mapper stays the same, you can replace it whenever you need.

The use of factory would let you avoid tight coupling to specific class names and, in case of mappers for SQL databases, let you inject in every instance same DB connection.

To learn a bit more about it, you could read this, this and this.

But, if you are seriously thinking about studying OOP, then reading this book is mandatory.

by anonymous   2017-08-20

I would suggest that you familiarize yourself with the history of PHP, I know that doing so has given me a much greater appreciation of what PHP is today and where it has come from.

In short, PHP was written by Rasmus Lerdorf to provide simple wrapper functions for the C code that was actually doing the heavy-lifting so that he could have a simpler language / syntax for writing templates that needed to behave dynamically. The growth of PHP and the community which surrounds it is best described as organic. And much like other things that grow organically, its more than a little messy, asymmetrical, and downright non-congruent.

Once you understand PHP and its community, you need to embrace PHP for everything that it is and everything that it is not. This idea was best presented by Terry Chay in his article PHP without PHP. He's specifically talking about the concept of funky caching, but he captures the concept of coding for PHP as if it were PHP and not (insert favorite language here) better than anyone I've ever seen. In other words, don't try to make PHP into Java, C#, Ruby, etc because if you do you'll fail and you'll hate your life.

Take a look at How is PHP Done the Right Way?.

I must say that you must first, last, and always avoid the tendency of most beginning PHP developers to use the spaghetti-code anti-pattern. In other words, if you find that you're writing code that contains sql queries, manipulation of data, validation of data, and html output all in a single php script, then you're doing it wrong.

In order to avoid this, it will be helpful to learn something about the nature of web-oriented design patterns. This of course precludes a familiarity with object-oriented programming. But once you've learned the basics of object-oriented programming in PHP, study the MVC design pattern. You don't have to implement this exactly but using the basic ideas of Model-View-Controller will allow you to avoid the blob script problem that most newbies tend to create.

On this point, I would strongly recommend that you take any code snippets you find on the web with a grain of salt. And even if you find it in a book you'll have to consider how old the book is. PHP as a language has advanced quite a long ways and you can't just take code samples at face value because, depending on their age, they may be using workarounds that were valid in 3.x or 4.x but simply are no longer necessary with newer features.

One great thing to do is to study the various frameworks out there. Evaluate what you like and what you don't. Maybe even work up each of the quickstarts that are provided with the framework documentation so that you can start to get an idea of what you like and don't like. And I would strongly recommend that you review the code from the frameworks as well as several other open-source projects so that you can get a feel for how others do things in PHP. Again, take it all with a grain of salt because every PHP developer has their own pet peeves and nuances and none of us is right all the time. In fact, most of the time with PHP there are going to be several pretty good ways to do something.

If you want to get a better understanding of the patterns that are being implemented by the frameworks and are commonly thrown around in the common vernacular on SO, I would suggest that you read Fowler and GoF. They'll teach all about the basic design patterns you'll use in your development efforts.

Specifically watch for the following:

  • Function files that contain LOTS of functions. This is most likely representative of a need to either put functions directly in the scripts that need them or it may also indicate an opportunity to create some more generic functions that can be made to fulfill the roles of a couple of highly specific functions. Of course, if you're building cohesive, well-encapsulated classes you shouldn't run into this problem.
  • The do everything class. This is a blob anti-pattern and is really nasty. In this case you need to identify where cohesion and encapsulation are breaking down and use those points as opportunities to break up the class into several smaller, more maintainable classes.
  • SQL queries that don't use parameterized queries or at least escaped parameters. Very, very, very bad.
  • Any instance where validation is not being performed or is only performed client-side. When developing for the web, the only way to keep your site and your users safe is to assume that everyone else is a black hat.
  • A sudden obsessive desire to use a template engine. PHP is a templating language. Make certain that you have clear reasons for adding another layer onto your website before using a template engine.

For further reading please look at the following:

PHP Application Design Patterns
Defend PHP -- useful to give you an idea of the most common criticisms.
Security of strip_tags and mysqlirealescapestring
What should Every PHP Programmer Know
How to Structure an ORM
Best Way to Organize Class Hierarchy
Main Components / Layers of PHP App
Why use Framework for PHP
Recommended Security Training for PHP

by anonymous   2017-08-20

Instead of that dreadful abomination, you should learn how to utilize spl_autoload_register():

spl_autoload_register( function( $classname ){

    $filename = 'inc/classes/' . $classname . '.class.php';

    if ( !file_exists( $filename) ){
        throw new Exception("Could not load class '$classname'.". 
                            "File '$filename' was not found !");
    }

    require $filename;

});

And you should register the autoloader in your index.php or bootstrap.php file, and do it only once per loader (this ability lets you define multiple loaders, but that's used, when you have third party library, which has own autoloader .. like in case of SwiftMailer).

P.S. please learn to use prepared statements with MySQLi or PDO.

Update

Since you are just now learning OOP, here are few things, which you might find useful:

Lectures:

  • Advanced OO Patterns
  • Inheritance, Polymorphism, & Testing
  • Recognizing smelly code
  • Global State and Singletons
  • Don't Look For Things!

Books:

by anonymous   2017-08-20

The cause

The root of your problem is misuse of active record pattern. AR is meant for simple domain entities with only basic CRUD operations. When you start adding large amount of validation logic and relations between multiple tables, the pattern starts to break apart.

Active record, at its best, is a minor SRP violation, for the sake of simplicity. When you start piling on responsibilities, you start to incur severe penalties.

Solution(s)

Level 1:

The best option is the separate the business and storage logic. Most often it is done by using domain object and data mappers:

  • Domain objects (in other materials also known as business object or domain model objects) deal with validation and specific business rules and are completely unaware of, how (or even "if") data in them was stored and retrieved. They also let you have object that are not directly bound to a storage structures (like DB tables).

    For example: you might have a LiveReport domain object, which represents current sales data. But it might have no specific table in DB. Instead it can be serviced by several mappers, that pool data from Memcache, SQL database and some external SOAP. And the LiveReport instance's logic is completely unrelated to storage.

  • Data mappers know where to put the information from domain objects, but they do not any validation or data integrity checks. Thought they can be able to handle exceptions that cone from low level storage abstractions, like violation of UNIQUE constraint.

    Data mappers can also perform transaction, but, if a single transaction needs to be performed for multiple domain object, you should be looking to add Unit of Work (more about it lower).

    In more advanced/complicated cases data mappers can interact and utilize DAOs and query builders. But this more for situation, when you aim to create an ORM-like functionality.

    Each domain object can have multiple mappers, but each mapper should work only with specific class of domain objects (or a subclass of one, if your code adheres to LSP). You also should recognize that domain object and a collection of domain object are two separate things and should have separate mappers.

    Also, each domain object can contain other domain objects, just like each data mapper can contain other mappers. But in case of mappers it is much more a matter of preference (I dislike it vehemently).

Another improvement, that could alleviate your current mess, would be to prevent application logic from leaking in the presentation layer (most often - controller). Instead you would largely benefit from using services, that contain the interaction between mappers and domain objects, thus creating a public-ish API for your model layer.

Basically, services you encapsulate complete segments of your model, that can (in real world - with minor effort and adjustments) be reused in different applications. For example: Recognition, Mailer or DocumentLibrary would all services.

Also, I think I should not, that not all services have to contain domain object and mappers. A quite good example would be the previously mentioned Mailer, which could be used either directly by controller, or (what's more likely) by another service.

Level 2:

If you stop using the active record pattern, this become quite simple problem: you need to make sure, that you save only data from those domain objects, which have actually changed since last save.

As I see it, there are two way to approach this:

  1. Quick'n'Dirty

    If something changed, just update it all ...

    The way, that I prefer is to introduce a checksum variable in the domain object, which holds a hash from all the domain object's variables (of course, with the exception of checksum it self).

    Each time the mapper is asked to save a domain object, it calls a method isDirty() on this domain object, which checks, if data has changed. Then mapper can act accordingly. This also, with some adjustments, can be used for object graphs (if they are not to extensive, in which case you might need to refactor anyway).

    Also, if your domain object actually gets mapped to several tables (or even different forms of storage), it might be reasonable to have several checksums, for each set of variables. Since mapper are already written for specific classes of domain object, it would not strengthen the existing coupling.

    For PHP you will find some code examples in this ansewer.

    Note: if your implementation is using DAOs to isolate domain objects from data mappers, then the logic of checksum based verification, would be moved to the DAO.

  2. Unit of Work

    This is the "industry standard" for your problem and there is a whole chapter (11th) dealing with it in PoEAA book.

    The basic idea is this, you create an instance, that acts like controller (in classical, not in MVC sense of the word) between you domain objects and data mappers.

    Each time you alter or remove a domain object, you inform the Unit of Work about it. Each time you load data in a domain object, you ask Unit of Work to perform that task.

    There are two ways to tell Unit of Work about the changes:

    • caller registration: object that performs the change also informs the Unit of Work
    • object registration: the changed object (usually from setter) informs the Unit of Work, that it was altered

    When all the interaction with domain object has been completed, you call commit() method on the Unit of Work. It then finds the necessary mappers and store stores all the altered domain objects.

Level 3:

At this stage of complexity the only viable implementation is to use Unit of Work. It also would be responsible for initiating and committing the SQL transactions (if you are using SQL database), with the appropriate rollback clauses.

P.S.

Read the "Patterns of Enterprise Application Architecture" book. It's what you desperately need. It also would correct the misconception about MVC and MVC-inspired design patters, that you have acquired by using Rails-like frameworks.

by anonymous   2017-08-20

In a respones to a question in the same area that I've ased, someone suggested the book Patterns of Enterprise Application Architecture by Martin Fowler. I've yet to read, but it seems to have good reviews.

by anonymous   2017-08-20

The Decorator Pattern is probably the most straight forward one to use and would be a good one to extend concrete objects functionality and/or characteristics.

Here is some light reading: Head First Design Patterns - CH3 pdf

FYI, couple must have's for learning and referencing design patterns regardless your language of choice:

1) Head First Design Patterns

2) Patterns for Enterprise Application Architecture

3) Design Patterns: Elements of Reusable Object-Oriented Software

And sites:

1) DoFactory

2) StackOverflow Design Patterns Newbie

There are a few others, I'll have to dig them up.

by anonymous   2017-08-20

You should read books like

Martin Fowler - Patterns of Enterprise Application Architecture

Dino Esposito - Microsoft® .NET: Architecting Applications for the Enterprise

But they can be too difficult if you are C# entry level developer (and not senior Java developer same time :))

Some overview and basic understanding you can get here or search some short and easy articles about this subject.