4

I am a very beginner writing one of my first webapps. I'm using FastAPI and I'm stuck on the logic of creating an endpoint that has to do a lot of things before it returns something back to the user. Since I'm new I also lack a lot of the vocabulary that I think I'd have if I were more experienced so bear with me:

To start -- What is my web app doing?

Well, I am trying to create something that will pull a schedule from a 3rd party API and then show it to me, allowing me to book things from my webapp rather than having to navigate to the 3rd party APIs. Some of these events are recurring (or are assumed to be) and get recorded as a "preference"


My approach so far, using 'MVVM'

  1. User navigates to www.myfakeurl.com/schedule (either directly or from the home page)

  2. This sends a request to the /schedule router

(A) view model task 1: call 3rd party API and wait for a response

  1. In the ScheduleViewModel, there's no payload to check, so I send a request using the current user's email + token to access the 3rd party API's own schedule page (this is done by get_current_schedule() in services/3p_schedule.py)

(B) view model task 2: validate / parse the 3rd party API response

  1. Back in ScheduleViewModel, I validate the response. The 3rd party API sends over a lot of stuff I don't need, so I just keep a few relevant pieces of info (like booking_id, if I already am signed up for a class, etc)

(C) view model task 3: compare schedule against user preferences database table and note any overlap

  1. Still in ScheduleViewModel, I quickly check against the UserPreferences table (in the SQLAlchemy DB) if any classes in the schedule match my preferences and create a is_a_preferred_time attribute for each class for when I add the schedule info to my database

(D) view model task 4: construct a dict from steps 4 and 5 and return it

  1. Still in the ScheduleViewModel, I finally combine the dict of info from step 4 with my info about user preferences from step 5. The schedule is a List[Dict[str, dict]] where the outer dict has the date and the inner dict keys are user_id (from step 3), time (from 3rd party API), is_preferred (from UserPreferences table), booking_id (from 3rd party API), currently_enrolled (from 3rd party API)

  2. I use the dict to populate a template.


My concerns and confusions

TLDR -- Basically, the goal of hitting the /schedule endpoint is to return an exercise class schedule from the 3rd party API + some info regarding user preferences (eg "Wednesdays at 7:30 am") if it's in the schedule (eg, maybe a different font color if there's a class time that matches your preferences)... Given that this is the end goal, it takes a few steps to get there and I am wondering if (A), (B), (C), and (D) noted in bold font results in the view model violating the single responsibility principle, and, if yes, if that could be solved by simply abstracting away much of the logic of A-D into a separate function outside the view model

I started off using pydantic for a lot of this, and then I learned about MVVM and got confused and just went with MVVM since I don't know what I am doing and it seemed more clear.

But, this feels like a lot for one endpoint to handle ? Or maybe the examples in all my tutorials are just very basic and real-life is closer to what I have going on?

I apologize in advance for just how clueless I am, but I don't have anyone to ask about this and am looking for any and all guidance / resources. I think I am in a very steep section of the learning curve atm :)

8
  • 1
    You have a lot of good information in this question, but it is difficult to identify the actual question. Can you clarify what the problem is? Be sure to focus on one problem where the community can identify a single answer. Avoid asking more than one question per post. Jul 26 at 18:26
  • Noted -- edited in the post above. There's a TLDR in bold now at the end. I hope that makes things more clear for people -- I'd love advice.
    – Jinx
    Jul 26 at 19:15
  • 1
    That helps focus the question, but "doing too many things" is very open-ended. Open-ended questions are opinion-based, which does not fit the format of this community. Is there a specific problem you have identified with this class doing A, B, C and D? Jul 26 at 19:21
  • I am very new to design patterns / coding outside of a 'get it to work for the physics problem set' mindset but I was under the impression that a function should have one job, a class should have one job, a view should have one job, etc. etc. and by doing so you will likely write code that is easier to understand, maintain, and debug. So, "too many things" here just means 'does this count as technically more than one'? And am happy to receive pushback if I'm, with my learner's foggy eyes, not understanding something correctly.
    – Jinx
    Jul 26 at 19:26
  • 1
    Opinion is off-topic for every StackExchange site. "Good design" is a matter of opinion. So is "bad design." The challenge is identifying an objective set of criteria for the design. Jul 26 at 19:32

1 Answer 1

2

Here is a fluff free digest of your steps:

  1. Visiting www.myfakeurl.com/schedule...

  2. ...sends a request to the /schedule router

  3. In ScheduleViewModel, access the 3rd party API schedule page as user (this is done by get_current_schedule() in services/3p_schedule.py)

  4. In ScheduleViewModel, validate the relevant info in the response. (like booking_id, if signed up for a class, etc)

  5. In ScheduleViewModel, if any classes in the schedule match UserPreferences (a table in the SQLAlchemy DB) then create a is_a_preferred_time attribute

  6. In ScheduleViewModel, Combine the dict of info from step 4 with user preferences info from step 5. The schedule is a List[Dict[str, dict]] where the outer dict has the date and the inner dict keys are user_id (from step 3), time (from 3rd party API), is_preferred (from UserPreferences table), booking_id (from 3rd party API), currently_enrolled (from 3rd party API)

  7. I use the dict to populate a template.

Digesting further I see some dependencies:

  1. 3rdPartySchedule <- Response <- Relevant Info
  2. 3rdPartySchedule <- Matching UserPreferences <- is_a_preferred_time
  3. 4 & 5

By A <- B I mean you first need to know A if you want to calculate B.

What I see here is simple data retrievals and transformations.

And I see ScheduleViewModel doing all this work and you asking if it's doing to much or if this work should be abstracted away.

And also I see you've been convinced that we'll only talk about this if the question is dedicated to a particular design principle.

Well I'm sorry. This isn't just about the Single Responsibility Principle. That idea is just one facet of what you're scratching at. It's the flip side of another idea: Separation of Concerns. They are very similar in execution but the names drive you to different blogs, books, and authors.

The Single Responsibility Principle says a module should only have one reason to change. Only one thing should have the power to dictate design change to it.

This buzzword will send you to study the wisdom of Robert Martin

Separation of Concerns is when "one is willing to study in depth an aspect of one's subject matter in isolation for the sake of its own consistency, all the time knowing that one is occupying oneself only with one of the aspects."

This buzzword will send you to study the wisdom of Dijkstra

Both of them fall under what I consider the most important design question to ask, "What knows about what?"

I'll admit that last one will probably drive you to my posts. But it's the question that every UML class diagram tries to answer with those arrows. They show you what knows about what.

That's important because while Robert wants only one reason to change in every box, and Dijkstra wants strong box walls that let you focus, I want to know what boxes know about what boxes. Because when boxes know too much about each other you really aren't getting anything good from having the boxes.

Basically, the goal of hitting the /schedule endpoint is to return an exercise class schedule from the 3rd party API + some info regarding user preferences (eg "Wednesdays at 7:30 am") if it's in the schedule (eg, maybe a different font color if there's a class time that matches your preferences)... Given that this is the end goal, it takes a few steps to get there and I am wondering if (A), (B), (C), and (D) noted in bold font results in the view model violating the single responsibility principle, and, if yes, if that could be solved by simply abstracting away much of the logic of A-D into a separate function outside the view model

Can you decompose these steps? Sure. Should you? Ah, here's the crux. Robert Martin wants one reason to change in every box. Dijkstra wants to ignore distractions. I want you to not pack everything away into tiny boxes unless you can make those boxes talk to each other in a sensible way.

Because when you move complexity out of the box it shows up between the boxes. Fight this by limiting knowledge of other boxes and their internal details. Fight this with good names over good abstractions that hide details. Give me good names that keep me from having to keep looking inside the box to remember what it does. Don't surprise me when I look inside. Organize the boxes well so I can find what I'm looking for. Give me that and you can have your multitude of boxes (classes, functions, closures, methods, variables, or whatever identifier).

Fail to do that and I don't think your boxes are doing any good. Boxes that know so much about each other that they must always change together haven't given you anything but extra code noise.

I really could see everything you're describing happening in one class and in one function. It might sound like a lot but that's not why we decompose. We decompose because we see a single idea hiding among others that we can explain better on its own, as it's own abstraction, with it's own name. If you can't see a way to pull that out then leave it alone.

1
  • This was incredibly, incredibly helpful (and I totally agree my post is a mess. If I had to write it again I think I could do better, but that is only thanks to my discussion with Greg above and your answer). Thank you :)
    – Jinx
    Jul 27 at 15:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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