emoss08 / Trenova

An Open Source AI-driven asset based transportation management system
http://trenova.app
Other
24 stars 5 forks source link

Add: Concurrency Handling #244

Closed emoss08 closed 5 months ago

emoss08 commented 5 months ago

Add Optimistic locking that allows handling of concurrent update to entities. This will ensure that an update only occurs if the data has not changed since it was last read.

Controller Example:

// UpdateCommentType updates a comment type.
func (r *CommentTypeOps) UpdateCommentType(ctx context.Context, commentType ent.CommentType) (*ent.CommentType, error) {
    // Retrieve the current state of the comment type.
    current, err := r.client.CommentType.Get(ctx, commentType.ID)
    if err != nil {
        wrappedErr := eris.Wrap(err, "failed to retrieve comment type")
        r.logger.WithField("error", wrappedErr).Error("failed to retrieve comment type")
        return nil, wrappedErr
    }

    // Check if the version matches.
    if current.Version != commentType.Version {
        return nil, tools.NewValidationError("Comment type has been updated by another user. Please refresh and try again",
            "syncError",
            "name")
    }

    // Start building the update operation
    updateOp := r.client.CommentType.UpdateOneID(commentType.ID).
        SetStatus(commentType.Status).
        SetDescription(commentType.Description).
        SetName(commentType.Name).
        SetSeverity(commentType.Severity).
        SetVersion(commentType.Version + 1) // Increment the version

    // Execute the update operation
    updatedCommentType, err := updateOp.Save(ctx)
    if err != nil {
        return nil, err
    }

    return updatedCommentType, nil
}

Entity Example:

// Fields of the CommentType.
func (CommentType) Fields() []ent.Field {
    return []ent.Field{
        field.Enum("status").
            Values("A", "I").
            Default("A").
            SchemaType(map[string]string{
                dialect.Postgres: "VARCHAR(1)",
                dialect.SQLite:   "VARCHAR(1)",
            }).
            StructTag(`json:"status" validate:"required,oneof=A I"`),
        field.String("name").
            NotEmpty().
            MaxLen(10).
            SchemaType(map[string]string{
                dialect.Postgres: "VARCHAR(10)",
                dialect.SQLite:   "VARCHAR(10)",
            }).
            StructTag(`json:"name" validate:"required,max=10"`),
        field.Enum("severity").
            Values("High", "Medium", "Low").
            Default("Low").
            SchemaType(map[string]string{
                dialect.Postgres: "VARCHAR(6)",
                dialect.SQLite:   "VARCHAR(6)",
            }).
            StructTag(`json:"severity" validate:"required,oneof=High Medium Low"`),
        field.Text("description").
            Optional().
            StructTag(`json:"description" validate:"omitempty"`),
        field.Int("version").
            Optional().
            StructTag(`json:"version" validate:"omitempty"`),
    }
}

Note: We can probably add the version to the BaseMixin so that all entities will have this.