DEV Community

5 reasons why people are choosing Masonite over Django

Joseph Mancuso on February 27, 2019

Introduction Masonite is an increasingly popular Python web framework that has garnered a lot of attention in the Python community recen...
Collapse
 
yoongkang profile image
Yoong Kang Lim • Edited

Thanks for introducing me to Masonite, it looks like it has a good API, and I especially like the matcher-style routing.

Would just like to offer a few minor corrections. Firstly, Django's ORM doesn't use the Data Mapper pattern as you described -- it also uses Active Record. In the Active Record pattern, your domain objects contain the logic that handle persistence and querying, which clearly describes Django's models (it even says so here: docs.djangoproject.com/en/2.2/misc...). Perhaps you're confusing Django's ORM with SQLAlchemy, which allows you to use the Data Mapper pattern.

Also, regarding middleware, your comparison between Masonite and Django code examples isn't exactly fair -- they're not doing comparable things, because the auth handling logic in Masonite is clearly done elsewhere, while you put everything in the Django's middleware example. In addition, that is really bad Django code.

In addition, Django's middleware API has also been updated recently -- the one true way to write middleware in Django is now just a function/callable. Here's how Django's middleware that is comparable to your Masonite middleware would look like, assuming it is ordered after the default auth middleware:

def global_auth_middleware(get_response):
    def middleware(request):
        if not request.user.is_authenticated:
            return redirect('/login/')
        return get_response(request)
    return middleware
Enter fullscreen mode Exit fullscreen mode

So, clearly those two code samples you provided comparing Django and Masonite middleware do not do the comparison justice.

Masonite's fine-grained middleware control is a neat idea though.

Collapse
 
josephmancuso profile image
Joseph Mancuso

Fair points. I'll update my post.

As for the Django middleware example, how do you only execute it before or after the request with this style? What would be an example of middleware executing after a request?

Collapse
 
yoongkang profile image
Yoong Kang Lim • Edited

I'm not sure what you mean by "before" a request -- the whole request/response cycle is initiated by a request from the client, anything before that is outside the cycle.

There are hooks you can use for when the request has started (but hasn't finished), or after a http response has been sent to the client (docs.djangoproject.com/en/dev/ref/...), but I must say I've never used them. If I needed to be doing that, I'd be writing WSGI middleware instead, or using something like Celery/Django Channels to do a task asynchronously.

Thread Thread
 
josephmancuso profile image
Joseph Mancuso

yeah been a while since I personally used Django. I think it was beginning of 1.11ish I believe. Just trying to see if there's something comparable to the Masonite middleware example above where its:

--> execute a "before" middleware (cleaning the request, setting some variables on some classes, redirecting for auth, etc)

--> execute the controller method (django view)

--> execute an "after" middleware (setting response headers, setting status code, converting controller method response (i.e dictionary to json converting) etc)

Thanks though!

Thread Thread
 
yoongkang profile image
Yoong Kang Lim

Hmm, maybe I'm misunderstanding you, but:

--> execute a "before" middleware (cleaning the request, setting some variables on some classes, redirecting for auth, etc)

You mean like this?

def global_auth_middleware(get_response):
    def middleware(request):

        if not request.user.is_authenticated:
            return redirect('/login/')
        # before logic here
        request.some_property = Property.objects.get(request['HTTP_X_PROPERTY_ID'])
        return get_response(request)
    return middleware

--> execute an "after" middleware (setting response headers, setting status code, converting controller method response (i.e dictionary to json converting) etc)

Could be something like this.

def global_auth_middleware(get_response):
    def middleware(request):
        if not request.user.is_authenticated:
            return redirect('/login/')
        # 'after' logic here
        response = get_response(request)
        response['X-Custom-Header'] = 'somevalue'
        return response
    return middleware
Collapse
 
spookylukey profile image
Luke Plant

Could you explain why the pattern of not defining database fields on the model itself, but loading them via inspection? I know that Rails does it that way, so some people must prefer it, but I simply cannot imagine wanting that. It seems to have lots of disadvantages.

For example:

  • You're going to need to open a DB tool to know what attributes are available. So without a running app, you can't do the most basic checks on something - or you have to read through and mentally execute the entire migration stack to understand what the current state of the DB schema is, rather than having it all defined in Python.

  • For relationships you need to define it in code (according to this - orator-orm.com/docs/0.9/orm.html#r... ) so the DB fields stuff only goes so far, and know it is split between two places.

  • Computed properties (e.g. a full_name convenience property that combines first_name and surname) are going to be in code, but DB fields in the DB. If you come across my_model_instance.full_name, you won't know whether it is defined in the DB or in Python, so you won't know where to look first.

  • If you want to define additional field info that can't be defined in DB schema (e.g. validation or something like that), there is nowhere for that.

So I'm genuinely curious why this design would be considered an advantage. I always thought that the Rails design was just a quirk of how DHH like to develop.

Collapse
 
josephmancuso profile image
Joseph Mancuso

Good questions. All valid. Laravel does it as well so it usually is the preference at least in both rails and PHP ecosystems. Python seems to have gone the way of how Django does models.

You're going to need to open a DB tool to know what attributes are available. So without a running app, you can't do the most basic checks on something - or you have to read through and mentally execute the entire migration stack to understand what the current state of the DB schema is, rather than having it all defined in Python.

This is correct and this has always been quite a pain point for these types of systems. One of the ways we get around that in Masonite is we have a craft command called craft model:docstring table_name which will generate something like this:

"""Model Definition (generated with love by Masonite)

id: integer default: None
name: string(255) default: None
email: string(255) default: None
password: string(255) default: None
remember_token: string(255) default: None
created_at: datetime(6) default: CURRENT_TIMESTAMP(6)
updated_at: datetime(6) default: CURRENT_TIMESTAMP(6)
customer_id: string(255) default: None
plan_id: string(255) default: None
is_active: integer default: None
verified_at: datetime default: None
"""
Enter fullscreen mode Exit fullscreen mode

you can then slap that on the docstring on your model so you know the details of what is in the table. Just a way to get around that issue.

As for mentally executing the migration stack, I'm not 100% sure what you mean. There is always a migrations table in these types of systems that manage the migrations that have ran and have not run so its usually just a simple command and it will spit back out the state of the database in terms of migrations.

For relationships you need to define it in code (according to this - orator-orm.com/docs/0.9/orm.html#r... ) so the DB fields stuff only goes so far and know it is split between two places.

not really sure what you mean by this one. You don't define relationships on the database level. You can define constraints like foreign key constraints if that is what you mean. But that is a constraint and not necessarily a relationship. If that is what you mean, even if you define a constraint you still need to specify the relationship in code. Getting an entity and its relationships has to do with the query you send to the database.

Django relationships I believe are accessed a little more implicit inside the core codebase but either way, they are defined in code. In order to map a relationship, you need to compile a specific query related to the column of the table either using an implicit or explicit join.

But yeah, either way, you define relationships at the codebase level. You would want to anyway because you can better tweak the relationship. Maybe you only want to limit it to 10 results or order by descending or only show where active is 1.

Computed properties (e.g. a full_name convenience property that combines first_name and surname) are going to be in code, but DB fields in the DB. If you come across my_model_instance.full_name, you won't know whether it is defined in the DB or in Python, so you won't know where to look first.

If you always check the model first you hit 2 birds with 1 stone, if it's not on the model then it's in the table. I wouldn't consider this a bad thing. Whether it's on the model or in the database you still check the model. Plus its easier to check the model than the database usually so yeah. Just check the model. If it's not on the model then its not a computed property and its instead a column on the database.

If you want to define additional field info that can't be defined in DB schema (e.g. validation or something like that), there is nowhere for that.

Why not? Firstly, many of these types of ORM's have hooks that you can tie into like saving or creating. Validation can be done here perfectly fine. Also, validation doesn't have to be at the model level. I can just as easily validate incoming data before I do anything with models. I do this at the controller / business logic to validate all incoming data pretty easily.

So I'm genuinely curious why this design would be considered an advantage. I always thought that the Rails design was just a quirk of how DHH like to develop.

I don't think its a quirk since it's actually a pretty validated design. Laravel itself has been doing it extremely successfully and after using both Django and Laravel (I used Django style first) I much prefer the Laravel style (which is the same Orator and this style above).

I think one of the biggest advantages is a lot of the time on projects, especially in corporations, you are using an existing database. This is the perfect advantage for this type of ORM because with something like Django you need to recreate the entire schema in Python anyway. Yes, there have since been tools to aid in this like database inspection tools to create Python schemas for you but that seems more like an afterthought to me.

I think you should use both systems and experience it for yourself. If you prefer the Django or SQLalchemy way more than more power to you. People have preferences and I fully understand that and that is ok.

Collapse
 
mozillalives profile image
Phil

I agree that this project is interesting and I do like some design considerations.

One design I greatly dislike though is the models. I remember when Django and Rails were first gaining popularity and many of my friends pointed out how cool it was that Rails could infer your model properties from the database schema. As I've gained more experience though I've come to realize that this can be quite annoying and even in some cases dangerous. I very much consider the explicit Django model a plus and would avoid this dynamic ORM.

Interesting work though.

Collapse
 
josephmancuso profile image
Joseph Mancuso

i'm curious, why is it annoying? and why do you think it's dangerous?

Collapse
 
shayneoneill profile image
shayneoneill • Edited

Masonites use of "dynamic" active record is a step backwards. We tried that with Rails. It turned out to be an anti-pattern. (A recurring problem with that confounded ORM. The guy who designed it really did have some ass-backwards views on ORM design. I was baffled at his insistence that constraints didnt belong in the DB.)

I dunno. Some of this I like, but if feels like we're making old mistakes over again.

Collapse
 
josephmancuso profile image
Joseph Mancuso

why do you consider it an anti pattern and how does "non dynamic" active record solve the pattern?

Collapse
 
jasoncrowe profile image
Jason Crowe

Great intro article. It has piqued my interest.

Collapse
 
josephmancuso profile image
Joseph Mancuso

Can't wait for you to join the community :)

Collapse
 
bhupesh profile image
Bhupesh Varshney 👾 • Edited

Will there be a 'django-rest-framework' alternative in Masonite ?
If yes
I am going all over Masonite 😋😋🔥🔥🔥

Collapse
 
josephmancuso profile image
Joseph Mancuso

Yes. There is already the beginnings of ones people are already using called Masonite API and can be found in the docs

Collapse
 
abdurrahmaanj profile image
Abdur-Rahmaan Janhangeer

Thanks, did not know of Masonite!