11

I am trying to follow the DRY principle in my programming as hard as I can. Recently I have been learning design patterns in OOP and I have ended up repeating myself quite a bunch.

I have created a Repository pattern along with a Factory and Gateway patterns to handle my persistence. I'm using a database in my application but that shouldn't matter since I should be able to swap out the Gateway and switch to another kind of persistence if I wished.

The problem I've ended up creating for myself is that I create the same objects for the number of tables I have. For example these will be the objects I need to handle a table comments.

class Comment extends Model {

    protected $id;
    protected $author;
    protected $text;
    protected $date;
}

class CommentFactory implements iFactory {

    public function createFrom(array $data) {
        return new Comment($data);
    }
}

class CommentGateway implements iGateway {

    protected $db;

    public function __construct(\Database $db) {
        $this->db = $db;
    }

    public function persist($data) {

        if(isset($data['id'])) {
            $sql = 'UPDATE comments SET author = ?, text = ?, date = ? WHERE id = ?';
            $this->db->prepare($sql)->execute($data['author'], $data['text'], $data['date'], $data['id']);
        } else {
            $sql = 'INSERT INTO comments (author, text, date) VALUES (?, ?, ?)';
            $this->db->prepare($sql)->execute($data['author'], $data['text'], $data['date']);
        }
    }

    public function retrieve($id) {

        $sql = 'SELECT * FROM comments WHERE id = ?';
        return $this->db->prepare($sql)->execute($id)->fetch();
    }

    public function delete($id) {

        $sql = 'DELETE FROM comments WHERE id = ?';
        return $this->db->prepare($sql)->execute($id)->fetch();
    }
}

class CommentRepository {

    protected $gateway;
    protected $factory;

    public function __construct(iFactory $f, iGateway $g) {
        $this->gateway = $g;
        $this->factory = $f;
    }

    public function get($id) {

        $data = $this->gateway->retrieve($id);
        return $this->factory->createFrom($data);
    }

    public function add(Comment $comment) {

        $data = $comment->toArray();
        return $this->gateway->persist($data);
    }
}

Then my controller looks like

class Comment {

    public function view($id) {

        $gateway = new CommentGateway(Database::connection());
        $factory = new CommentFactory();
        $repo = new CommentRepository($factory, $gateway);

        return Response::view('comment/view', $repo->get($id));
    }
}

So I thought I was using design patterns correctly and keeping good practices, but the problem with this thing is that when I add a new table, I have to create the same classes just with other names. This raises suspicion in me that I may be doing something wrong.

I thought of a solution where instead of interfaces I had abstract classes which using the class name figure out the table they need to manipulate but that doesn't seem like the right thing to do, what if I decide to switch to a file storage or memcache where there are no tables.

Am I approaching this correctly, or is there a different perspective I should be looking at?

  • When you create a new table, do you always use the same set of SQL queries (or an extremely similar set) to interact with it? Also, is the Factory encapsulating any meaningful logic in the real program? – Ixrec Aug 22 '15 at 21:27
  • @Ixrec commonly there will be custom methods in the gateway and repository that perform more complex sql queries like joins, the problem is that the persist, retrieve and delete functions - defined by the interface - are always the same except for the table name and possibly but unlikely the primary key column, so I have to repeat those in every implementation. The factory very rarely holds any logic and at times I skip it at all and have the gateway return the object instead of the data, but I created a factory for this example since it should be the proper design? – Emilio Rodrigues Aug 22 '15 at 21:33
  • I'm probably not qualified to give a proper answer, but I get the impression that 1) the Factory and Repository classes aren't really doing anything useful, so you'd be better off ditching them and working with just Comment and CommentGateway directly 2) It should be possible to put the common persist/retrieve/delete functions in a single place rather than copy-pasting them, perhaps in an abstract class of "default implementations" (kinda like what the Collections in Java do) – Ixrec Aug 22 '15 at 21:58
12

The problem you are addressing is quite fundamental.

I have experienced the same problem when I worked for a company that made a large J2EE application that consisted of several hundred web pages and over a million and a half lines of Java code. This code used ORM (JPA) for persistence.

This problem get worse when you use 3rd party technologies in every layer of the architecture and the technologies all require their own data representation.

Your problem can not be solved at the level of the programming language you are using. Using patterns is good but as you see it cause repetition of code (ore more accurately put: repetition of designs).

The way I see it there are only 3 possible solutions. In practice these solutions come down to the same.

Solution 1: Use some other persistence framework that allows you to state only what must be persisted. There is probably such a framework around. The problem with this approach is that it is rather naive because not all you patterns will be persistence related. You wil also want to use patterns for user interface code so you'd then need a GUI framework that can re-use the data representations of the persistence framework you choose. If you can not re-use them you'll need to write boiler plate code to bridge the data representations of the GUI framework and the persistence framework.. and this is contrary to the DRY principle again.

Solution 2: Use another - more powerful - programming language that has constructs that allow you to express the repetitive design so you can re-use the design-code. This is probably not an option for you but suppose for a moment it is. Then again when you start creating a user interface on top of the persistence layer you'll want the language again to be powerful enough to support creating the GUI without having to write boiler plate code. It is unlikely that there is a language powerful enough to do what you want since most languages rely on 3rd party frameworks for GUI building that each require their own data representation to work.

Solution 3: Automate the repetition of code and design using some form of code generation. Your worry is about having to hand-code repetitions of patterns and designs since hand-coding repetitive code/design violate the DRY principle. Nowadays there are very powerful code generator frameworks out there. There are even "language workbenches" that allow you to quickly (half a day when you have no experience) create your own programming language and generate any code (PHP/Java/SQL - any thinkable text file) using that language. I have experience with XText but MetaEdit and MPS seem to be fine as well. I strongly advice you to check one of these language workbenches out. To me it was the most liberating experience in my professional life.

Using Xtext you can have your machine generate the repetitive code. Xtext even generates a syntax highlighting editor for you with code completion for your own language specification. From that point you simply take your gateway and factory class and turn them into code templates by punching holes in them. You feed them to your generator (which is called by a parser of your language that is also completely generated by Xtext) and the generator will fill in the holes in your templates. The result is generated code. From that point on you can take out any repetition of code anywhere (GUI code persistence code etc).

  • Thank you for the reply, I have seriously taken code generation into consideration and I am even starting to implement a solution. They are 4 boilerplate classes so I guess I could do that in PHP itself. While this does not solve the repeating code issue I think that the tradeoffs are worth it - having highly maintainable and easily changeable even though repetitive code. – Emilio Rodrigues Aug 23 '15 at 16:31
  • This is the first I have heard of XText and it looks very powerful. Thank you making me aware of this! – Matthew James Briggs Aug 24 '15 at 8:19
8

The problem you facing is an old one: code for persistent objects often looks similar for each class, it is simply boilerplate code. That's why some smart people invented Object Relational Mappers - they solve exactly that problem. See this former SO post for a list of ORMs for PHP.

When existing ORMs do not suffer your needs, there is also an alternative: you can write your own code generator, which takes a meta description of your objects to persist and generates the repeating part of the code from that. That is actually not too hard, I did this in the past for some different programming languages, I am sure it will be also possible to implement such things also in PHP.

  • I have created such functionality but I switched from it to this because I used to have the data object handle data persistence tasks which does not comply with SRP. For example I used to have Model::getByPK method and in the above example I would be able to do Comment::getByPK but getting the data from the database and constructing the object is all contained within the data object class, which is the problem I am trying to solve using design patterns. – Emilio Rodrigues Aug 23 '15 at 7:09
  • ORMs do not have to place the persistence logic in the model object. This is the Active Record pattern, and while popular there are alternatives. Have a look at what ORMs are available, and you should find one that doesn't have this problem. – Jules Aug 23 '15 at 9:26
  • @Jules that is a very good point, it got me thinking and I was wondering - what would be the problem of having both an ActiveRecord and a Data Mapper implementations available in my application. Then I could use each one of those when I need them - this will solve my problem of rewritting the same code by using the ActiveRecord pattern and then when I actuallly need a data mapper it wouldn't be such a pain to create the necessary classes for the job? – Emilio Rodrigues Aug 23 '15 at 11:35
  • 1
    The only problem I can see with this right now is that working out the edge cases when a query needs to join two tables where one uses Active Record and the other is managed by your Data Mapper - it would add a layer of complexity that otherwise wouldn't arise. Personally, I'd just use the mapper - I've never liked Active Record from the beginning - but I know that's just my opinion, and others disagree. – Jules Aug 23 '15 at 12:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.