Mapping and Validating with Marshmallow

In this article I explain the 3-tier architecture. From that I explain the need to have a mapping with Marshmallow. I will use Marshmallow to map my database entities to JSON objects.

Content

  • The 3-tier architecture
  • Marshmallow
  • From data to presentation
  • From presentation to data
  • Error Handler

Here is an explanatory video with more details.

All the code is available in the following repository.

As I said, Marshmallow will map my database entities to JSON objects to be returned to the frontend. But why do I need this mapping?

The 3-tiers architecture

A backend application is usually split into multiple layers: the database layer, the front end and the service. There are other architectures with more layers, but let’s keep it simple. This is called the three tier architecture. And the name of each tier are: the data tier, the presentation tier, and the application tier.

In the data tier, I have my entities which reflect the database structure. I may have some logic there, related to a single entity. Like parsing or mapping single fields or dates, building the full name from the first name and the last name, or some other simple logic.

In the presentation tier is mostly located the front end, with all the logic dedicated to the user behavior. How the user can interact with my application. How my application is presented to the user.

And finally, the application tier contains the business logic. Here will be the cross entities logic. Like calculating a price from the unit price of each element within a basket and with the user promotions.

And now, why do I need to map my database entities to the front-end entities? Now that I have three layers, making a single object travel, as it is, the three layers isn’t a good idea. The need at each layer isn’t the same. In the database, I need my entities to be as normalized as possible. Separated in multiple tables. With their relationships. Nevertheless, in the front end, I need some entities to be grouped and have some aggregations or calculations done in some fields. Because I have different needs, I need different objects with different structures. I may even need to hide some fields, as the password. The password field must never be returned from the database to the front end. Marshmallow come in this step, making the mapping easy from the database tier to the presentation tier.

Marshmallow

First of all, let’s add the Marshmallow dependency with Poetry. As I will serialize objects coming from my database, from SQLAlchemy, I already have a dependency that fits perfectly that need.

poetry add marshmallow-sqlalchemy

This will also add the raw Marshmallow dependency.

From data to presentation

I will now create some entities which serialize my SQLAlchemy object.

class Country(db.Model):
    __tablename__ = country_table_name

    id = db.Column(db.Integer, primary_key=True)
    code = db.Column(db.String(2), unique=True, nullable=False)
    name = db.Column(db.String(50), unique=True, nullable=False)


class CountrySchema(SQLAlchemyAutoSchema):
    class Meta:
        model = Country
        include_relationships = True
        load_instance = True

The parent class, SQLAlchemyAutoSchema, will automatically detect the type of each field and adapt the JSON document returned. I must specify the model Country which will be used to inherit the fields. I will also indicate to include the relationships, the children objects. And the load_instance will create the Group object when deserializing the JSON document, will create the Group object with the reverse mapping. Let’s now use this schema.

country_schema = CountrySchema()


@country_bp.route("", methods=["GET"])
def get_all_countries():
    countries = db.session.scalars(select(Country)).all()
    return jsonify(country_schema.dump(countries, many=True))

Specifying many=true, it will return a list of objects. I will create another example

class User(db.Model):
    __tablename__ = user_table_name

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password = db.Column(db.Text, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)

    country_id = db.Column(db.Integer, db.ForeignKey(Country.id))

class UserSchema(SQLAlchemySchema):
    class Meta:
        model = User
        include_relationships = True
        load_instance = True

Nevertheless, this way, when serializing the object User to a JSON document, it will export the password information. I don’t want to send the encrypted password outside my system. This can be easily exploited. For that, I must indicate which fields to map.

class UserSchema(SQLAlchemySchema):
    class Meta:
        model = User
        include_relationships = True
        load_instance = True

    id = auto_field()
    username = auto_field()
    email = auto_field()

With auto_field, it will detect automatically the type of the field.

From presentation to data

Okay, I’ve mapped some entities from the data tier to the presentation tier. But Marshmallow can also be used to map entities from the presentation tier to the data tier. I will now create some raw Marshmallow schemas for a particular case. I want an entity to validate the incoming body to contain the right fields when authenticating. I want to ensure that the body contains only the field’s username and password when authenticating.

class CredentialsSchema(Schema):
    username = fields.String(required=True)
    password = fields.String(required=True)

    @post_load
    def make_user(self, data, **kwargs):
        return User(**data)

And if the schema is correctly validated, it creates the User object with the @post_load decorator. This User object will be used to search the user information in the database.

Let’s see the last example. The user creation with some particular validation. When creating a user, I want that the password has at least 8 characters, with uppercase and lowercase. I also want to verify that the email field really contains an email.

class UserCreationSchema(Schema):
    username = fields.String(required=True)
    password = fields.String(required=True)
    email = fields.String(required=True)

    @validates("password")
    def validates_password(self, value):
        if len(value) < 8:
            raise ValidationError("Password must longer than 8")
        if not any(char.isupper() for char in value):
            raise ValidationError("Password must contain upper case")
        if not any(char.islower() for char in value):
            raise ValidationError("Password must contain lower case")

    @validates("email")
    def validates_email(self, value):
        if not re.match("[^@]+@[^@]+\.[^@]+", value):
            raise ValidationError("Invalid email format")

    @post_load
    def make_user(self, data, **kwargs):
        return User(**data)%

The @validates decorator identifies a method to be run to validate the content of a given field. In the previous case, I have two validators: for the password and for the email. And as before, create the User object if everything is correct with the @post_load decorator.

Error Handler

Okay, all of those validations are great. But I will enrich my error handler to return a particular error when a validation exception occurs while parsing, while mapping, the fields.

@error_bp.app_errorhandler(ValidationError)
def handle_invalid_data(error):
    print(traceback.format_exc())
    return jsonify({"message": "Incorrect format data"}), 400

Now, every time a validation error occurs, I will see this particular error message. In the error object, I can also access the field which was incorrectly set and the desired output if i want a more verbose message.

Conclusion

  • I’ve added the Marshmallow SQLAlchemy dependency, which contains the raw Marshmallow dependency and a wrapper for SQLAlchemy.
  • I’ve created schemas to serialize and deserialize the SQLAlchemy objects. The fields are automatically detected with SLQAlchemyAutoSchema.
  • If I want a custom mapping, I can specify the individual fields and auto_field to detect its type.
  • I’ve also created some raw Marshmallow schemas to map incoming objects from the front end. I’ve used the @validate decorator for custom validation. And the @post_load decorator to return a specific object when the validation is done.
  • Finally, I’ve added the validation error in my error handler to return a custom message when the validation isn’t done correctly.

References

Repository


Never Miss Another Tech Innovation

Concrete insights and actionable resources delivered straight to your inbox to boost your developer career.

My New ebook, Best Practices To Create A Backend With Spring Boot 3, is available now.

Best practices to create a backend with Spring Boot 3

Leave a comment

Discover more from The Dev World - Sergio Lema

Subscribe now to keep reading and get access to the full archive.

Continue reading