Back

Heavy question: how abstract should your domain be from the framework you build it in?


Consider the common User Registration/Authentication system. A robust registration system will have the following characteristics:

  1. User authentication
  2. An option for tracking logged in users (seeing how many people are online, where they are)
  3. Standard password reset/recovery options
  4. A double authentication for admin login that creates a separate session and changes the session ID for security
  5. User groups
  6. Hooks to reach out to a service like StopForumSpam for anti-spam
  7. The ability to choose whether users can immediately log in after registration, or must click a validation link in their email (and with #6 overriding that behavior if a suspicious user is detected)
  8. Hooks for newsletter and lead generation services
  9. Hooks for A/B testing welcome emails etc....

There are two basic ways to build such a system (or any system)

(1) Everything is fairly tightly coupled to Laravel's Events, Emails, Authentication, Hashing, User object etc. E.g. your RegistrationController might use a RegistrationHandler, but that Handler (or any event listeners) directly utilize Laravel's stuff.

Example:

class RegistrationHandler
{
       public function register(Array $data)
       {
           // Register the user
           ....

           // Fire Event
           Event::fire('user.registered', array($User)); // tightly coupled to Laravel Events
       }
 }

(2) Everything is completely abstracted either through interfaces, or through adapters. E.g. your RegistrationHandler would take an EventDispatcherInterface, which is essentially an adapter for various implementations. E.g. you would code a throwaway LaravelEventDispatcher adapter.

Example:

class RegistrationHandler
{
      protected $EventDispatcher;

      public function __construct(EventDispatcherInterface $EventDispatcher)
      {
            $this->EventDispatcher = $EventDispatcher
       }

       public function register(Array $data)
       {
           // Register the user
           ....

           // Fire Event
           $this->EventDispatcher->fire('user.registered', array($User)); // abstracted away from Laravel Events, which leads to ....
       }
}

class LaravelEventDispatcher implements EventDispatcherInterface
{
      public function fire($key, Array $payload)
      {
             Event::fire($key, $payload);
      }
}          

While #2 makes your domain more portable between frameworks, it also results in a LOT of extra work coding the proper abstractions. At which point I would argue you've lost a key benefit to using a framework in the first place: reasonably low time investment to get features out the door.

I realize that #2 helps make code more testable, not just more portable, but I'm a bit torn between writing "clingy" code (e.g. leveraging Laravel directly) rather than taking the time to abstract all of it out.

Keep in mind that I'll be building a registration/authentication system similar to the above, along with a signifcant amount of other domain-level features. Writing proper abstractions for all of it would incur a fairly significant overhead, and I don't really have any desire to port it to a different framework in the future (especially given how elegant Laravel is). Even if 4.19 is the last version of Laravel ever released, I would rather just patch Laravel myself as needed, than worry about porting the domain to another framework, which may or may not even have the same feature set that Laravel has.

What have you guys traditionally done when faced with a large domain? Really commit to your framework to keep abstraction overhead to a minimum, or develop your domain in a framework-agnostic way?

popolla replied 5 years ago

I appreciate this analysis. Personally, I think most of us would avoid complicating things to slightly facilitate an improbable transition to another framework.

rizqidjamaluddin replied 5 years ago

I have recently taken a strong turn towards stringent interface contracting and making everything as abstract and interchangeable as possible.

A few things I've learned along the way:

  • It's useful to decide, very early on, what rigid dependencies you're willing to tolerate. For instance, most of my applications are written to have a requirement for Laravel's core, but everything else is built to interfaces. Some of my projects outright delete the Facades for Auth. Others might even want to be Framework-agnostic. Pick one, commit to it, and make sure it's written in stone.
  • It always looks like there's more scaffolding when you start the classes (new service providers, new classes, more namespaces, more interfaces, etc) but that's a one-time deal. Much of this is easily copy-pasted between different pieces of code.
  • I started off by feeling that the verbose code was against the whole idea of using a framework, but then I realized that the bulk of the work is still handled by Laravel and its minions; most of the code I write is just verbose because that's how PHP demands namespacing, class declarations etc to work.
  • Testability and swappability are the key considerations. It's easy to get pushed into the "I'll probably never have to switch away from [x], screw IoC/interfaces" mindset, so I think of it differently; building code in this manner allows me to have multiple functioning implementations of each sub-system. At first glance it might make sense for everything to be tightly coupled, but when the day comes (more on this in a second) that you need to replace a segment of code, sure you could just start wiping out your existing class and writing your new logic on top of it... Or you could code to the interface in a new implementation, and just swap over an IoC container binding.

That last point is key for me: if a project I'm working on is serious enough for "when the day comes" scenarios (e.g. if my messaging system gets too big, might I be asked to move to a separate dedicated messaging API on a separate server, keep data on redis/memcache, use a different Mongo DB instead of the main MySQL, etc), then I will abstract the hell out of my project.

It might cost a small amount of time to build the additional scaffolding, but in a large project, decoupling code is way more time-intensive and error-prone. If you foresee this happening in the future, make sure you're ready for it!

carbontwelve replied 5 years ago

I use some of the packages I write on other frameworks than Laravel. For simple projects we usually use Silex with Laravel and Symphony for the big application work - so for my situation having abstraction built in from the set go is quite important - but then its also built into the scaffolding tool-chain I built to make such extra work moot.

TD:LR; if you don't expect to have to use the code you write for any other framework, then there is no harm is tightly coupling it to the framework you use - just keep in mind that code needing testing has enough abstraction in place to allow it.

ShawnMcCool replied 5 years ago

Lean software development, do only what it takes to get the business requirements satisfied with competency. Write good OOP and test your code so that any refactoring (will be needed with like every new feature ever) will be painless.

Decisions like decoupling from the framework aren't real decisions unless you have that as a business requirement. Instead, focus on just writing good OOP code. You'll automatically have a solution that's abstract enough for the moment and easy enough to refactor.

I recommend Uncle Bob's video series on fundamentals on http://cleancoders.com

Additionally, Sandi Metz has a book called Practical Object-Oriented Design with Ruby that is extremely relavent to a PHP developer.

sheldonkibbler replied 5 years ago

ShawnMcCool said: Instead, focus on just writing good OOP code. You'll automatically have a solution that's abstract enough for the moment and easy enough to refactor.

So, I'm curious. In this actual example would doing it as way (1) from above (tightly coupled to Laravel) be considered writing good OOP code?

keithmifsud replied 4 years ago

Hi Guys,

I know this is quite an old post now but I thought I'd share my way of handling events in a DDD fashion.

As Shawn said, the decision to decouple code from a framework might be a business requirement, however, more than not this is not the case.

My way of working is to always decouple the code from the framework, but this is not to just satisfy the possible requirement of a new framework but for satisfying a lot more principles such as the SRP. Furthermore, it makes perfect sense to invest 5 minutes one each provider by decoupling them from their actual implementation using an interface. This will yield to a lot of benefits, mainly extending of your software but also to have everything organised the same way.

So, I handle events this way (but I also handle everything this way from Auth, Validation, Payment providers etc..)

Let's say I have an eCommerce application, which allows users to register and on registration a User event listener needs to get a cookie with the Cart id to link the cart to the new user.

First of all the events provider itself:

Ecommerce/Providers/EventDispatcher: EventDispatcherInterface (contract) LaravelEventDispatcher (implementation) EventDispatcherServiceProvider (binding) AbstractEvent (just an abstract class which has the above dependency) AbstractEventListener (just an abstract class which has the above dependency)

Then the User events are grouped into one class - I like this approach so that I have a list of events for each resource(entity etc..)

Ecommerce/Events/UserEvents (extends AbstractEvent ) - this class only fires events.

Then the Cart needs to listen to the above event: Ecommerce/Events/Cart/CartEventListeners (extends AbstractEventListener ) - this class only listens to events and calls the handler which is normally a separate service.

If anyone would like to see the code for these just let me know and I'll share a Gist or something similar.

In reality this takes only a few minutes more to setup but you save several minutes while working because you know exactly where things are. Not to mention how easy it is to test it and extend on it.

My opinion is that working this way makes me feel a lot more confident when myn projects are handed over to larger firms and support companies. I know for a fact that good devs can understand my approach and not so good devs will love learning from it :)

philippzentner replied 4 years ago

@keithmifsud

Do you consequently work with loCs?

keithmifsud replied 4 years ago

philippzentner said:

@keithmifsud

Do you consequently work with loCs?

@philippzentner I don't understand the question in relation to the thread?

Steve-45 replied 3 years ago

I guess you should try using "Ahsay backup software" as this software can surely resolve your problem and most important i can give backup to large files as well. So give it a try.


Sign in to participate in this thread!



We'd like to thank these amazing companies for supporting us