1

I have an old Codeigniter project I am working on and I am trying to improve the codebase. Basically I am trying to decouple Codeigiter where I can and implement unit tests in the process.

Currently I am working on refactoring the User workflow: specifically adding or updating a User. As it stands, there is a controller method called "save" which handles everything including the following:

  • check if username exists
  • validate password
  • update or insert User
  • create a permission record (user can have many permissions)
  • save User uploaded image to temp folder
  • crop User uploaded image and save to temp folder
  • upload cropped User image to S3 bucket
  • update User with image
  • redirect back to User index

As you can imagine, this controller is HUGE (and this is one of the more simple workflows in the app).

My biggest problem at the moment is being unable to test this due to all the coupling. I just have no idea where to start. I did a ton of reading in the last few weeks and here are my current thoughts:

  • Create a service object which handles the whole process called UserSaveService. This would allow the controller to just call the service method and handle the redirects and flash errors/successes. This service would handle updating/inserting the user using the Codeigniter ActiveRecord models.
  • Create objects (command object?) for each major component
  • UploadImage: accepts a form name -> handles uploading image to temp folder -> returns a path of the uploaded image
  • CropImage: accepts a path and crop dimensions -> crops the image and stores it in temp folder -> returns a path of the cropped image
  • UploadToS3: accepts a file path (in this case a cropped image) and a bucket identifier -> uploads the file to S3 -> returns S3 url

My structure currently looks like:

application (codeigniter)
src
-- services
---- SaveUserService.php
-- commands
---- UploadImage.php
---- CropImage.php
---- UploadToS3.php

Does anyone have any recommendations? I would love to know what sort of patterns could be used here! I do realize this is a broad question so I hope it does not get closed, I just have no idea where else to take this kind of discussion. Thank you so much!

Roeland
  • 3,698
  • 7
  • 46
  • 62
  • I hope this answers your question, if so would appreciate marking as answer. If not let me know how i can expand on my answer and help you – morleyc Oct 29 '14 at 08:49

1 Answers1

2

You cant really get away from the application entry point (Save User) that is huge. I wouldn't worry about the size (in terms of the functionality and what it does), I would worry about how it is coupled for testing as you have said.

Decoupling by modules

I would work on splitting the code in to services (as Greg Young says this is probably the most overloaded term in software!), that the main application (Save User) can call, so as you have listed:

  • ImageUploader
  • ImageCropper

I would make each of the above have an interface, and then derive from this interface the specific implementation (to allow for different file storage services from different providers, and to also allow mocking during unit tests of an empty class that doesn't really to any uploading and just returns success).

You can then use dependency injection/inversion-of-control container to hang everything together (for simpler programs you may not need an IoC container), this would be initialized during boot of the application (or at the start of the test, where you would specify the mock implementations to use). You may find this link interesting Why do I need an IoC container as opposed to straightforward DI code?

Decoupling by messages

If you found that you were facing the need to handle business risk of services being down (i.e. uploading images to a remote file server, what if this were down, how would you handle this?) causing the entire flow to break, then you could think about decoupling using messages (NewUser message, attaching the picture in this message, firing off on a service bus where a subscriber is listening)... this would decouple things in time, but it can come with additional overhead in infrastructure and your system would become eventually consistent.

SOLID Principles

It is invaluable to follow the SOLID principles in writing software, and you have already started by noting the specific responsibility of the components and started to split them yup.

From Wikipedia on SOLID principles you will want to consider the following:

S - Single responsibility principle a class should have only a single responsibility (i.e. only one potential change in the software's specification should be able to affect the specification of the class)

O - Open/closed principle “software entities … should be open for extension, but closed for modification.”

L - Liskov substitution principle “objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.” See also design by contract.

I - Interface segregation principle “many client-specific interfaces are better than one general-purpose interface.”

D - Dependency inversion principle one should “Depend upon Abstractions. Do not depend upon concretions.” Dependency injection is one method of following this principle.

Recommended Books

Some good books from Uncle Bob really helped me with the above, a good google search would find plenty of similar material:

  • Clean Code: A Handbook of Agile Software Craftsmanship
  • Agile Software Development, Principles, Patterns, and Practices
  • The Clean Coder: A Code of Conduct for Professional Programmers
Community
  • 1
  • 1
morleyc
  • 2,169
  • 10
  • 48
  • 108