1

I'm working on a project with the task of creating a route to checkout ticket purchases for events. In this scenario, I have a controller that receives the request with order data, client information, tickets, and payment details. Subsequently, I pass this request to my use cases to perform the tasks.

However, I have a use case where I create an Order, which is an entity, and I need another use case to receive the Order to validate the tickets using the Tickets entity. Later, if the validation is successful, the Order is passed to the payment use case, creating the Transaction entity. All of this is done in the same controller, somewhat like in the code below:

    export class CheckoutController implements Controller {
    
      constructor(
       private createOrder: CreateOrder,
       private makeTicketValidate: MakeTicketValidate,
       private makePayment: MakePayment,
       ){}
    
      async handle (request: CheckoutController.Request): Promise<HttpResponse> {
    
         const order: Order = this.createOrder(request)
         // Here i receive the Order entity and step to the next use case
    
         const isValid: boolean = await this.makeTicketValidate(order)
    
         if(!isValid) {
           return badRequest()
         }
         
         const transactionId = await this.makePayment(order)
    
         if(!transactionId) {
           return internalServerError()
         }
        
         return ok({transactionId})
         
      }
    }

Is passing the Order entity through the controller from one use case to another a code smell?

Thank you in advance to everyone who responded!

I've been considering using a data access object for the 'orders' entity to transfer data between the use cases.

Alternatively, I could separate the controllers to divide the responsibilities.

6
  • I don't see "use cases". I see other methods in your controller. Typically use cases are their own abstraction which forms its own cluster of cohesive classes separate from the controller. That said, there isn't anything inherently wrong with your implementation (at least without defining what "wrong" means). You've got multiple questions here. Can you edit your post to focus on a single problem? Otherwise, I'm afraid this will be closed as needing focus. Commented Feb 8 at 19:31
  • 1
    ok let me edit to focus on central question! Commented Feb 8 at 19:34
  • This is better, however a "code smell" is not as universally defined as you might think. What seems like a code smell to one person, might seem perfectly fine to another. Commented Feb 8 at 19:45
  • You mention "use cases" which makes me think of Clean Architecture, but I'm not seeing anything but controller code. Are you referring to use cases in Clean Architecture, or use cases in a more general sense? Commented Feb 8 at 19:47
  • But, according to Uncle Bob's Clean Architecture, he states that communication between layers should be done using data access objects, and that controllers should never handle an entity Commented Feb 8 at 19:49

2 Answers 2

1

I'm looking at this and I'm thinking, "If the order object is modified by any of those methods the overall function may be fragile" But I think this is mainly down to your naming rather than anything else.

The naming seems to imply side effects. makeTicketValid doesn't take or return a ticket. makePayment returns a transaction id, so i guess it kinda makes sense, But yeah I assume it's creating a payment behind the scenes as a side effect and hiding it from you.

The method input being called "request" also worries me, basically it's a viewbag of any data, why cant it be an entity of some kind instead?

You could have for example:

  async handle (order: Order): Promise<HttpResponse> {
     var isValid = await this.validateOrder(order)
     if(!isValid) { throw new NotValidOrderException();}

     await this.repo.saveOrder(order);

     var payment = await this.MakePaymentFromOrder(order)
     await this.repo.SavePayment(payment)

     var paymentResult = await this.paymentProcessor.TakePayment(payment)


     return ok({paymentResult})
  }

Now errors can throw and be handled as required, your methods are clear about what they do and the injected services have clear responsibilities.

The naming changes make it clear that there are no side effects, clarify what objects are expected and exposes a new payment object.

This extra object helps enforce the correct order of events, decouples your payment processing from your order object and clarifies your "Use Cases"

So, is it a code smell to pass the order object around multiple use cases? Well now that i've cleaned it up adding the extra object and making the use case for payment explicit does seem better to me.

Although originally I felt the code smell was just down to naming, maybe it is the reuse of order for stuff that doesn't really need a whole order, and in a similar way request not being an entity that are the root of this smell.

5
  • Thanks for your response, in fact, the makeTicketValidate class should be called makeTicketValidation, and this use case performs the function of checking whether tickets that are in the cart of the Order entity that will be purchased can be sold, whether the quantity of purchase tickets is available, whether the date when purchasing tickets matches the date of an event. That's why makeTicketValidation() returns a boolean. and the name of the params object is a reference to the parameters received in the controller to create the Order entity, and not the entity itself Commented Feb 8 at 20:37
  • I don't disagree with the answer but I find "If the order is modified by any of those methods we could be in trouble" a weird observation to point out. Barring commutative mathematical operations, there is no inherent standard that mandates that code should be able to behave the same when executed in a different order.
    – Flater
    Commented Feb 9 at 4:35
  • "order" as in the order object, but yes also order as in if the order object changes then the order of the operations becomes critical. Not strictly speaking a problem, but combined with the naming, it's a bit smelly to me.
    – Ewan
    Commented Feb 9 at 9:34
  • popped in an edit
    – Ewan
    Commented Feb 9 at 9:51
  • Oh wow the ambiguity of the word "order" for the given example completely passed me by. I guess my point still somewhat stands but yeah I should've really figured out what you meant.
    – Flater
    Commented Feb 11 at 23:35
0

On the face of it, I would not approve of your code, but not specifically for the reason you're asking. I don't think that controller methods themselves should be orchestrating your logic, that responsibility is best separated from the "web adapter"-like responsibility that controllers have.
I would generally advise that you shift the orchestration over to the application layer, likely as an intermediary between your controller and your use cases (based on what your current project structure seems to be).

It appears to me that the question you're really asking here is:

Is it okay for multiple use cases to use the same models?

And the answer here is, unsatisfyingly, "it depends".

An argument can be made that it's cleaner to independently define your use case's models, so that each use case can evolve independently. But at the same time, there may be scenarios where there is heavy reuse between your use cases, e.g. myriad ways to generate reports from the same data set (ergo return type).

Whether or not the benefit from not having duplicate the model outweight the slight inability for use cases to diverge from one another very much depends on your expectation whether you want these models to be able to diverge when needed, or if you can confidently argue that you always want these models to be identical to one another at all times.

This is not a question I can conclusively answer for you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.