Laravel, Lumen, a Database and Shared Models

Learning Laravel has been fun, frustrating and enlightening all at the same time.  While the online community has been great in community issues, fixes and general tips on implementing cool, efficient solutions, not everything I’ve needed to do with it thus far seems to exist.  I owe a massive amount of my success as a programmer/architect to the open source community around the world, so this blog is my attempt to give back and help someone else along the way.

NOTE: This was written at the time for Laravel 5.1. It’s possible future versions of Laravel will change some of this and I hope it does for the seeding issue.

The Code Base

The lastest hurdle that took me a while to surmount involved a project that uses a Laravel installation in one directory, a Lumen installation in another directory and a shared database between the two. The Laravel installation contains all public facing web tools, while the Lumen installation powers an API interface used by both the Laravel installation and native apps being developed for this project.

The Problem

I had several options in front of me and after discussing with colleagues, opted to go with keeping the API within it’s own Lumen installation.  We hope that this application is used enough to need the significant speed increases promised by Lumen over other frameworks, even Laravel.  This left us with a problem.  Our directory structure looked like this.

/api => Lumen installation /web => Laravel installation

With two code bases, the default location for models would be /api/app and /web/app, respectively.  Since both code bases are using the same database, this means that at the start, I was duplicating my models, which is ridiculously inefficient (and thusly unacceptable).

The Options

So how should this be solved?  I started brainstorming along with my colleagues who were aware of this issue and we came up with a variety of solutions, each with benefits and issues.

Option #1: Integrate subdomain into Laravel code base

Laravel has an awesome feature in which routes can be defined for subdomains using a routing group with the [domain] attribute.

 Route::group(['domain' => 'site.dev'], function(){ Route::get('/', 'PublicController@getIndex'); Route::get('/contact', 'PublicController@getContact'); }); Route::group(['domain' => 'api.site.dev'], function(){ Route::get('/','APIController@getIndex'); Route::get('/login', 'APIController@getLogin'); }); 

This way, the API paths are integrated into the same code base and it uses the same Models that the normal website does.  However, this also means the API stuck being integrated into the Laravel code base, which means if we want to deploy on it another server, it’s going to end up being deployed along with all the website code, whether we want it or not.  Also, even though we couldn’t find metrics between Laravel vs. Lumen performance, it WILL preform slower when it’s using the full Laravel installation instead of the stripped down Lumen.  So this option was rejected.

Option #2: Don??t share the database (make it Lumen only)

So if we need two separate code bases, what about having the models only in one application (API) and making the web code require the API to interact with the database.  Seems very elegant, right? Well, as we started drilling down into this option, we quickly realized that without direct model access or database integration, we were missing out on all the great Facades that come integrated directly into Lumen and Laravel.  Want to get an array of models?  You now have to wrap that data into a JSON array and create your own methods to manipulate it.  Not fun and not time efficient as a programmer.  So this option was rejected.

Option #3: Models in each code base with unique methods

Ok, so we need two code bases and we need some sort of model in each code base.  Why not have the models duplicated with the base data and then put methods unique to each in each model.  Seems ok right? Well, once again, as drilled down, we realized we were having to duplicate methods.  Both web and API need a login method.  Suddenly we had two mostly identical methods in two separate code bases.  What if we change the database and suddenly have to duplicate our change tickets?  Ugh.  So this option was rejected.

Option #4: Share base models & customize by extension

I spent WAY too much time Googling this solution and was never able to find it answered properly, which is a huge reason why I wanted to start this blog.  Hopefully this will help someone else. If you haven’t figured it out yet, this was the solution I went with and it’s working beautifully.  If I could create a way to house models outside of each code base and reference them within each code base, I could then extend them and make custom methods for web and api in the extended models in each code base.  So what did I do? I added two external directories to the code base:

 /api /database /migrations /share /Models /Tools /Views /web 

All my models went into the new /share/Models directory. I gave them a namespace of Share.

 <!--?php </p> <p>namespace Share;</p> <p>use stuff here;</p> <p>class User extends Model implements AuthenticatableContract, CanResetPasswordContract<br ?--> { use Authenticatable, CanResetPassword; } 

In both /api and /web, I added a namespace path in psr-4.  This is the magic.  After all my Googling, I never found this example and when I just said \”screw it\” and tried it, the damn thing worked.  I had made the assumption that I couldn’t define things in psr-4 in a directory ABOVE the installation directory.  Lo and behold, it works beautifully.

 \"psr-4\": { \"App\\\\\": \"app/\", \"Share\\\\\": \"../share/Models/\" } 

Now I’m onto something!  I have a model that is accessible via both /api and /web.  I can now extend this model to create any unique models I need that specific code base.  Within /web/app/Models, I create the extension:

 <!--?php </p> <p>namespace App\\Models;</p> <p>class User extends \\Share\\User<br ?--> { public function uniqueMethod() { // Web only code here. } } 

Similarly, the same extension model can be created in /api/app/Models and custom API code put there.  With a quick composer dump-autoload, it’s working perfectly and now both /api and /web can use the login methods.

The Database & Migrations

Ok, so we now have a base shared Model that both code bases can use and can be deployed via Git or however you like separately from the /api and /web code bases.  What about the database?  If /api is on it’s own machine and /web on another, and they share a database on a third machine, what about migrations and seeds? This is where the /database direction comes into play.  It should be said, this solution isn’t perfect, but I’m REALLY hoping the Laravel powers that be decide to implement something that ends up making this perfect.  In the meantime, this is how I do it. So if you’ve made it this far, I’m guessing you’re aware of php artisan migrate.  A cool feature here is that it actually can take a –path variable.  So guess what?  Parent directory paths also work.

php artisan –path=../database/migrations migrate

We can now condense and share migrations between the two code bases.  Pull your new code down to /api server and migrate there, or pull new code down to /web server and migrate there.  Either one works!

The Catch

So this all seems great, Julie, but what’s the catch?  So here it is.  While migrations have a –path variables, db:seed doesn’t.  So if you want to see from API, you still need to put seeders in that code base.  Similarly with web.  This could get confusing as to what needs to be seeded and where. My OCD wants the code to all live in one place, but what if we make an update to the /api code and all the seeds are living in /web?  During deployment, you then have to log in to both servers to deploy on /api and seed in /web.  It’s not the worst thing in the world, but I would much prefer to be able to create a /seed directory in the outermost /database directory and just seed from there.

Conclusion

So there it is, kids.

  • Put models outside both code bases
  • Define a shared namespace with a parent directory path
  • Extend each model in each code base for unique methods
  • Consolidate migrations into one directory
  • Weep into your coffee about the seeding split

I hope you enjoyed the ride and that this may help someone else out one day.  Stay strong, open source community.

  • fluxberkman

    Great article, I look forward to reading more

    • Thank you for reading! I’m trying to build a library of solutions that I haven’t been able to find elsewhere. I hope to write more often.

  • kahunacoder

    I thought this would be a common setup having laravel consume lumen api’s but your article is the only one I found that suggested a way to integrate the two frameworks. Thanks!