From ca214fbbca5bfc8d2f18282766b3c6292a8cd734 Mon Sep 17 00:00:00 2001 From: Carson Gross Date: Tue, 16 Jan 2024 11:52:50 -0700 Subject: [PATCH 01/16] add RxDB as sponsor --- www/content/_index.md | 44 +++++++++++++++++++++-------------------- www/static/img/rxdb.svg | 1 + 2 files changed, 24 insertions(+), 21 deletions(-) create mode 100644 www/static/img/rxdb.svg diff --git a/www/content/_index.md b/www/content/_index.md index c5011d6a..5f76cfcf 100644 --- a/www/content/_index.md +++ b/www/content/_index.md @@ -75,6 +75,7 @@ Thank you to all our generous - - - - - - - - + + + + + + - - + + - - + diff --git a/www/static/img/rxdb.svg b/www/static/img/rxdb.svg new file mode 100644 index 00000000..3fc5e75c --- /dev/null +++ b/www/static/img/rxdb.svg @@ -0,0 +1 @@ + From f2267f302a3bbfc31a982636c5ff9f2e977f4ae2 Mon Sep 17 00:00:00 2001 From: Carson Gross Date: Tue, 16 Jan 2024 18:21:33 -0700 Subject: [PATCH 02/16] mvc --- www/content/essays/_index.md | 1 + www/content/essays/mvc.md | 183 +++++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 www/content/essays/mvc.md diff --git a/www/content/essays/_index.md b/www/content/essays/_index.md index 79b2f336..1fe699f8 100644 --- a/www/content/essays/_index.md +++ b/www/content/essays/_index.md @@ -34,6 +34,7 @@ page_template = "essay.html" * [Why I Tend Not To Use Content Negotiation](@/essays/why-tend-not-to-use-content-negotiation.md) * [Template Fragments](@/essays/template-fragments.md) * [View Transitions](@/essays/view-transitions.md) +* [Model/View/Controller](@/essays/mvc.md) ### Complexity Very Very Bad * [The Grug Brained Developer](https://grugbrain.dev) diff --git a/www/content/essays/mvc.md b/www/content/essays/mvc.md new file mode 100644 index 00000000..eb0af68e --- /dev/null +++ b/www/content/essays/mvc.md @@ -0,0 +1,183 @@ ++++ +title = "Model/View/Controller (MVC)" +date = 2023-01-16 +updated = 2023-01-16 +[taxonomies] +author = ["Carson Gross"] +tag = ["posts"] ++++ + +A common objection I see to using htmx and hypermedia is something along the lines of: + +> The problem with returning HTML (and not JSON) from your server is that you'd probably also like to serve mobile +> apps and don't want to duplicate your API + +I have outlined in [another essay](@/essays/splitting-your-apis.md) that I think you should split your JSON API & your +hypermedia API up into separate components. + +In that essay I explicitly recommend duplicating your API, in order to +disentangle your "churny" web application API endpoints (that are HTML) and your +stable, regular & expressive JSON Data API. + +I think that in my discussions so far I have failed to communicate one major background concept: the +[Model/View/Controller](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) (MVC) +pattern. + +## An MVC Intro + +I was a little shocked to discover [in a recent podcast](https://www.youtube.com/watch?v=9H5VK9vJ-aw) that many younger +web developers don't have much experience with MVC, perhaps due to the Front-end/Back-end split that occurred when Single +Page Applications became the norm. + +MVC is a simple pattern that predates the web and can be used in programs nearly any program that offer a graphical interface. + +The rough idea is as follows: + +* A "Model" layer contains your ["Domain Model"](https://en.wikipedia.org/wiki/Domain_model). This layer contains the + domain logic specific to the application. So, for example, a contact management application will have contact-related + logic in this layer. It will not have references to visual elements in it, and should be relatively "pure". + +* A "View" layer contains the "view" or visual elements that are presented to the user. This layer often (although not always) + works with model values to present visual information to the user. + +* Finally, a "Controller" layer, which coordinates these two layers: for example it might receive an update from a user, + update a Model and then pass the updated model to a View to display an update user interface to the user. + +There are a lot of variations, but that's the rough idea. + +Nearly on in web development, a lot of server side frameworks explicitly adopted the MVC pattern. The framework +that I'm most familiar with is [Ruby On Rails](https://rubyonrails.org/), which has documentation on each of these +topics: [Models](https://guides.rubyonrails.org/active_record_basics.html) that are persisted to the database, +[Views](https://guides.rubyonrails.org/action_view_overview.html) for generating HTML views from Models, and +[Controllers](https://guides.rubyonrails.org/action_controller_overview.html) that coorindate between the two. + +The rough idea, in Rails, is: + +* Models collect your application logic and database accesses +* Views take Models and generate HTML via a templating langauge (this is where HTML escaping is done, btw) +* Controllers take HTTP Requests and typically perform some action with a Model and then pass that Model on to a + View (or redirect, etc.) + +This is fairly standard, if "shallow" implementation of an Object-Oriented MVC pattern, using the underlying HTML, HTTP Request +lifecycle. + +### Fat Model/Skinny Controller + +A concept that came up a lot in the Rails community was the notion of +["Fat Model, Skinny Controller"](https://riptutorial.com/ruby-on-rails/example/9609/fat-model--skinny-controller). The +idea here is that your Controllers should be relatively simple, only maybe invoking +a method on the Model and then immediately handing the result on to a View. The Model, on the other hand, could +be much "thicker" with lots of domain specific logic. + +Let's keep that idea in mind as we work through a simple example of the MVC pattern. + +## An MVC-Style Web Application + +Let's take a look at one of my favorite examples, an online Contacts application. Here is a Controller method +for that application that displays a given page of Contacts by generating an HTML page: + +```python +@app.route("/contacts") +def contacts(): + contacts = Contact.all(page=request.args.get('page', default=0, type=int)) + return render_template("index.html", contacts=contacts) +``` + +Here I'm using [Python](https://www.python.org/) and Flask (https://flask.palletsprojects.com/en/3.0.x/), since I use +those in my [Hypermedia Systems](https://hypermedia.systems/) book. + +Here you can see that the controller is very "thin": it simply looks up contacts via the `Contact` Model object, passing +a `page` argument in. + +It then hands the paged collection of contacts on to the `index.html` template to render them to +an HTML page to send back to the user. + +The `Contact` Model, on the other hand may be relatively "fat" internally: it could have a bunch of domain logic +internally that does a database lookup, pages the data somehow, maybe applies some transformations or business rules, etc. + +### Creating A JSON Data API Controller + +Now, if you have this relatively well-developed model that encapsulates your domain, you can create a _different_ API +end point/Controller that does the same thing, but for JSON: + +```python +@app.route("/api/v1/contacts") +def contacts(): + contacts = Contact.all(page=request.args.get('page', default=0, type=int)) + return jsonify(contacts=contacts) +``` + +### But You Are Duplicating Code! + +Looking at these two controller functions, you may think "This is stupid, the methods are nearly identical". + +And you're right, at this point they are. + +However, let's consider two potential additions to our system. + +#### Rate Limiting Our JSON API + +First, let's add rate limiting to the JSON API to prevent DDOS or bad clients from swamping our system. We'll add the +[Flask-Limiter](https://flask-limiter.readthedocs.io/en/stable/) extension and use that: + +```python +@app.route("/api/v1/contacts") +@limiter.limit("1 per second") +def contacts(): + contacts = Contact.all(page=request.args.get('page', default=0, type=int)) + return jsonify(contacts=contacts) +``` + +Now, we don't want that limit applying to our web application, we just want it for our JSON Data API. Because we've +split the two up, we can achieve that. + +#### Adding A Graph To Our Web Application + +Let's consider another change: we want to add a graph to the `index.html` template above that is expensive to compute. + +Because we do not want to block the rendering of the `index.html` template, we will use the +[Lazy Loading](@/examples/lazy-load.md) pattern and create a new endpoint, `/graph`, to generate the HTML for that +graph: + +```python +@app.route("/graph") +def graph(): + graphInfo = Contact.computeGraphInfo(page=request.args.get('page', default=0, type=int)) + return render_template("graph.html", info=graphInfo) +``` + +Here, again, our Controller delegates out to the Model and then hands the results on to a View. + +Now, the thing to note here is that we've added a new endpoint to our web application API, but _we haven't added it to our JSON Data API_. So +we are not committing to other clients that this (specialized) endpoint will be around. + +And, since we are using + [Hypermedia As The Engine of Application State](@/essays/hateoas.md), we are free, in our web app, to remove or refactor this URL +later on. + +So, we get the [flexibility we want](@/essays/hypermedia-apis-vs-data-apis.md) for our hypermedia API, and the +[features](@/essays/hypermedia-apis-vs-data-apis.md) we want for our JSON Data API. + +And, because our domain logic has been collected in a Model, we can vary these two APIs flexibly while still achieving +a significant amount of code reuse. Our two APIs are decoupled, while our domain logic remains centralized. + +(This also gets at why [why I tend not to use content negotiation](@/essays/why-tend-not-to-use-content-negotiation.md) and return HTML & JSON from the same endpoint.) + +## MVC Frameworks + +Many older web frameworks such as [Spring](https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/mvc.html), +[ASP.NET](https://dotnet.microsoft.com/en-us/apps/aspnet/mvc), Rails have very strong MVC concepts that allow you to split +your logic out in this manner extremely effectively. + +Django has a variation on the idea called [MVT](https://www.askpython.com/django/django-mvt-architecture). + +This strong support for MVC is one reason why these frameworks pair very well with htmx. + +And, while the examples above are obviously biased towards [Object-Oriented](https://www.azquotes.com/picture-quotes/quote-object-oriented-programming-is-an-exceptionally-bad-idea-which-could-only-have-originated-edsger-dijkstra-7-85-25.jpg) +programming, the same ideas can be applied in a functional context as well. + +## Conclusion + +I hope that, if it is new to you, that gives you to a good feel for the concept of MVC and shows how that, by adopting that +organizational principle in your web applications, you can effectively decouple your API needs while at the same time avoiding +significant duplication of code. From 6a5e839eca8e3483dfefad0881d2f330d8bf6e50 Mon Sep 17 00:00:00 2001 From: Carson Gross Date: Tue, 16 Jan 2024 20:35:36 -0700 Subject: [PATCH 03/16] mvc --- www/content/essays/mvc.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/www/content/essays/mvc.md b/www/content/essays/mvc.md index eb0af68e..98687d85 100644 --- a/www/content/essays/mvc.md +++ b/www/content/essays/mvc.md @@ -12,22 +12,24 @@ A common objection I see to using htmx and hypermedia is something along the lin > The problem with returning HTML (and not JSON) from your server is that you'd probably also like to serve mobile > apps and don't want to duplicate your API -I have outlined in [another essay](@/essays/splitting-your-apis.md) that I think you should split your JSON API & your +I have already outlined in [another essay](@/essays/splitting-your-apis.md) that I think you should split your JSON API & your hypermedia API up into separate components. -In that essay I explicitly recommend duplicating your API, in order to -disentangle your "churny" web application API endpoints (that are HTML) and your +In that essay I explicitly recommend "duplicating" (to an extent) your API, in order to +disentangle your "churny" web application API endpoints that return HTML from your stable, regular & expressive JSON Data API. -I think that in my discussions so far I have failed to communicate one major background concept: the +In looking back at conversations I've had around this idea with people, I think that I have been assuming familiarity +with a pattern that, perhaps, many people are as deeply familiar with as I am: the [Model/View/Controller](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) (MVC) pattern. ## An MVC Intro I was a little shocked to discover [in a recent podcast](https://www.youtube.com/watch?v=9H5VK9vJ-aw) that many younger -web developers don't have much experience with MVC, perhaps due to the Front-end/Back-end split that occurred when Single -Page Applications became the norm. +web developers just don't have much experience with MVC. + +This is perhaps due to the Front-end/Back-end split that occurred when Single Page Applications became the norm. MVC is a simple pattern that predates the web and can be used in programs nearly any program that offer a graphical interface. From dddf15f1ad895d095a2978b1d0ccc42a4facb9fb Mon Sep 17 00:00:00 2001 From: Carson Gross Date: Tue, 16 Jan 2024 20:42:41 -0700 Subject: [PATCH 04/16] mvc --- www/content/essays/mvc.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/www/content/essays/mvc.md b/www/content/essays/mvc.md index 98687d85..ed4156a7 100644 --- a/www/content/essays/mvc.md +++ b/www/content/essays/mvc.md @@ -27,11 +27,10 @@ pattern. ## An MVC Intro I was a little shocked to discover [in a recent podcast](https://www.youtube.com/watch?v=9H5VK9vJ-aw) that many younger -web developers just don't have much experience with MVC. +web developers just don't have much experience with MVC. This is perhaps due to the Front-end/Back-end split that occurred when Single Page Applications became the norm. -This is perhaps due to the Front-end/Back-end split that occurred when Single Page Applications became the norm. - -MVC is a simple pattern that predates the web and can be used in programs nearly any program that offer a graphical interface. +MVC is a simple pattern that predates the web and can be with nearly any program that offers a graphical interface +to a user. The rough idea is as follows: From 01fc9cd3d87e024bdb553cf9a797ea6d3965e35a Mon Sep 17 00:00:00 2001 From: Carson Gross Date: Tue, 16 Jan 2024 21:01:28 -0700 Subject: [PATCH 05/16] mvc --- www/content/essays/mvc.md | 106 +++++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 41 deletions(-) diff --git a/www/content/essays/mvc.md b/www/content/essays/mvc.md index ed4156a7..1b77ea89 100644 --- a/www/content/essays/mvc.md +++ b/www/content/essays/mvc.md @@ -44,37 +44,40 @@ The rough idea is as follows: * Finally, a "Controller" layer, which coordinates these two layers: for example it might receive an update from a user, update a Model and then pass the updated model to a View to display an update user interface to the user. -There are a lot of variations, but that's the rough idea. +There are a lot of variations, but that's the idea. -Nearly on in web development, a lot of server side frameworks explicitly adopted the MVC pattern. The framework +Early on in web development many server side frameworks explicitly adopted the MVC pattern. The implementation that I'm most familiar with is [Ruby On Rails](https://rubyonrails.org/), which has documentation on each of these topics: [Models](https://guides.rubyonrails.org/active_record_basics.html) that are persisted to the database, -[Views](https://guides.rubyonrails.org/action_view_overview.html) for generating HTML views from Models, and -[Controllers](https://guides.rubyonrails.org/action_controller_overview.html) that coorindate between the two. +[Views](https://guides.rubyonrails.org/action_view_overview.html) for generating HTML views, and +[Controllers](https://guides.rubyonrails.org/action_controller_overview.html) that coordinate between the two. The rough idea, in Rails, is: * Models collect your application logic and database accesses -* Views take Models and generate HTML via a templating langauge (this is where HTML escaping is done, btw) -* Controllers take HTTP Requests and typically perform some action with a Model and then pass that Model on to a +* Views take Models and generate HTML via a templating langauge ([ERB](https://github.com/ruby/erb), this is where [HTML sanitizing](https://en.wikipedia.org/wiki/HTML_sanitization) is done, btw) +* Controllers take HTTP Requests and, typically, perform some action with a Model and then pass that Model on to a View (or redirect, etc.) -This is fairly standard, if "shallow" implementation of an Object-Oriented MVC pattern, using the underlying HTML, HTTP Request -lifecycle. +Rails has a fairly standard (although somewhat "shallow" and simplified) implementation of the MVC pattern, built on +top of the underlying HTML, HTTP Request/Response lifecycle. ### Fat Model/Skinny Controller -A concept that came up a lot in the Rails community was the notion of +One concept that came up a lot in the Rails community was the notion of ["Fat Model, Skinny Controller"](https://riptutorial.com/ruby-on-rails/example/9609/fat-model--skinny-controller). The idea here is that your Controllers should be relatively simple, only maybe invoking -a method on the Model and then immediately handing the result on to a View. The Model, on the other hand, could -be much "thicker" with lots of domain specific logic. +a method or two on the Model and then immediately handing the result on to a View. -Let's keep that idea in mind as we work through a simple example of the MVC pattern. +The Model, on the other hand, could be much "thicker" with lots of domain specific logic. (There are objections +that this leads to [God Objects](https://en.wikipedia.org/wiki/God_object), but let's set that aside for now.) + +Let's keep this idea of fat model/skinny controller in mind as we work through a simple example of the MVC pattern and +why it is useful. ## An MVC-Style Web Application -Let's take a look at one of my favorite examples, an online Contacts application. Here is a Controller method +For our example, let's take a look at one of my favorites: an online Contacts application. Here is a Controller method for that application that displays a given page of Contacts by generating an HTML page: ```python @@ -88,18 +91,24 @@ Here I'm using [Python](https://www.python.org/) and Flask (https://flask.pallet those in my [Hypermedia Systems](https://hypermedia.systems/) book. Here you can see that the controller is very "thin": it simply looks up contacts via the `Contact` Model object, passing -a `page` argument in. +a `page` argument in from the request. -It then hands the paged collection of contacts on to the `index.html` template to render them to +This is very typical: the Controllers job is to map an HTTP request into some domain logic, pulling HTTP-specific +information out and turning it into data that the Model can understand, such as a page number. + +The controller then hands the paged collection of contacts on to the `index.html` template, to render them to an HTML page to send back to the user. -The `Contact` Model, on the other hand may be relatively "fat" internally: it could have a bunch of domain logic -internally that does a database lookup, pages the data somehow, maybe applies some transformations or business rules, etc. +Now, the `Contact` Model, on the other hand, may be relatively "fat" internally: that `all()` method could have a bunch +of domain logic internally that does a database lookup, pages the data somehow, maybe applies some transformations or +business rules, etc. And that would be fine, that logic is encapsulated within the Contact model and the Controller +doesn't have to deal with it. ### Creating A JSON Data API Controller -Now, if you have this relatively well-developed model that encapsulates your domain, you can create a _different_ API -end point/Controller that does the same thing, but for JSON: +So, if we have this relatively well-developed Contact model that encapsulates our domain, you can easly create a +_different_ API end point/Controller that does something similar, but returns a JSON document rather than an HTML +document: ```python @app.route("/api/v1/contacts") @@ -110,16 +119,16 @@ def contacts(): ### But You Are Duplicating Code! -Looking at these two controller functions, you may think "This is stupid, the methods are nearly identical". +At this point, looking at these two controller functions, you may think "This is stupid, the methods are nearly identical". -And you're right, at this point they are. +And you're right, currently they are nearly identical. -However, let's consider two potential additions to our system. +But let's consider two potential additions to our system. #### Rate Limiting Our JSON API -First, let's add rate limiting to the JSON API to prevent DDOS or bad clients from swamping our system. We'll add the -[Flask-Limiter](https://flask-limiter.readthedocs.io/en/stable/) extension and use that: +First, let's add rate limiting to the JSON API to prevent DDOS or badly written automated clients from swamping our +system. We'll add the [Flask-Limiter](https://flask-limiter.readthedocs.io/en/stable/) library: ```python @app.route("/api/v1/contacts") @@ -129,16 +138,19 @@ def contacts(): return jsonify(contacts=contacts) ``` -Now, we don't want that limit applying to our web application, we just want it for our JSON Data API. Because we've -split the two up, we can achieve that. +Easy. + +But note: we don't want that limit applying to our web application, we just want it for our JSON Data API. And, because +we've split the two up, we can achieve that. #### Adding A Graph To Our Web Application -Let's consider another change: we want to add a graph to the `index.html` template above that is expensive to compute. +Let's consider another change: we want to add a graph of the number of contacts added per day to the `index.html` +template in our HTML-based web application. It turns out that this graph is expensive to compute. -Because we do not want to block the rendering of the `index.html` template, we will use the -[Lazy Loading](@/examples/lazy-load.md) pattern and create a new endpoint, `/graph`, to generate the HTML for that -graph: +We do not want to block the rendering of the `index.html` template on the graph generation, so we will use the +[Lazy Loading](@/examples/lazy-load.md) pattern for it instead. To do this, we need to create a new endpoint, `/graph`, +that returns the HTML for that lazily loaded content: ```python @app.route("/graph") @@ -147,22 +159,33 @@ def graph(): return render_template("graph.html", info=graphInfo) ``` -Here, again, our Controller delegates out to the Model and then hands the results on to a View. +Note that here, again, our controller is still "thin": it just delegates out to the Model and then hands the results on +to a View. -Now, the thing to note here is that we've added a new endpoint to our web application API, but _we haven't added it to our JSON Data API_. So -we are not committing to other clients that this (specialized) endpoint will be around. +What's easy to miss is that we've added a new endpoint to our web application HTML API, but _we haven't added it to +our JSON Data API_. So we are **not** committing to other non-web clients that this (specialized) endpoint, which +is being driven entirely by our UI needs, will be around forever. -And, since we are using - [Hypermedia As The Engine of Application State](@/essays/hateoas.md), we are free, in our web app, to remove or refactor this URL -later on. +Since we are not committing to *all* clients that this data will be available at `/graph` forever, and since we +are using [Hypermedia As The Engine of Application State](@/essays/hateoas.md) in our HTML-based web application, we are free to remove +or refactor this URL later on. + +Perhaps some database optimization suddenly make the graph fast to compute and we can include it inline in the +response to `/contacts`: we can remove this end point because we have not exposed it to other clients, it's just there +to support our web application. So, we get the [flexibility we want](@/essays/hypermedia-apis-vs-data-apis.md) for our hypermedia API, and the [features](@/essays/hypermedia-apis-vs-data-apis.md) we want for our JSON Data API. -And, because our domain logic has been collected in a Model, we can vary these two APIs flexibly while still achieving -a significant amount of code reuse. Our two APIs are decoupled, while our domain logic remains centralized. +The most important thing to notice, in terms of MVC, is that because our domain logic has been collected in a Model, +we can vary these two APIs flexibly while still achieving a significant amount of code reuse. Yes, there was a lot +of initial similarity to the JSON and HTML controllers, but they diverged over time. At the same time, we didn't +duplicate our Model logic: both controllers remained relatively "thin" and delegated out to our Model object to do +most of the work. -(This also gets at why [why I tend not to use content negotiation](@/essays/why-tend-not-to-use-content-negotiation.md) and return HTML & JSON from the same endpoint.) +Our two APIs are decoupled, while our domain logic remains centralized. + +(Note that this also gets at why [why I tend not to use content negotiation](@/essays/why-tend-not-to-use-content-negotiation.md) and return HTML & JSON from the same endpoint.) ## MVC Frameworks @@ -172,7 +195,8 @@ your logic out in this manner extremely effectively. Django has a variation on the idea called [MVT](https://www.askpython.com/django/django-mvt-architecture). -This strong support for MVC is one reason why these frameworks pair very well with htmx. +This strong support for MVC is one reason why these frameworks pair very well with htmx and those communities are excited +about it. And, while the examples above are obviously biased towards [Object-Oriented](https://www.azquotes.com/picture-quotes/quote-object-oriented-programming-is-an-exceptionally-bad-idea-which-could-only-have-originated-edsger-dijkstra-7-85-25.jpg) programming, the same ideas can be applied in a functional context as well. @@ -180,5 +204,5 @@ programming, the same ideas can be applied in a functional context as well. ## Conclusion I hope that, if it is new to you, that gives you to a good feel for the concept of MVC and shows how that, by adopting that -organizational principle in your web applications, you can effectively decouple your API needs while at the same time avoiding +organizational principle in your web applications, you can effectively decouple your APIs while at the same time avoiding significant duplication of code. From 0d8907e4b5165418221337338dbf974816d35990 Mon Sep 17 00:00:00 2001 From: Carson Gross Date: Tue, 16 Jan 2024 21:03:51 -0700 Subject: [PATCH 06/16] mvc --- www/content/essays/mvc.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/content/essays/mvc.md b/www/content/essays/mvc.md index 1b77ea89..3b4498a5 100644 --- a/www/content/essays/mvc.md +++ b/www/content/essays/mvc.md @@ -179,7 +179,9 @@ So, we get the [flexibility we want](@/essays/hypermedia-apis-vs-data-apis.md) f The most important thing to notice, in terms of MVC, is that because our domain logic has been collected in a Model, we can vary these two APIs flexibly while still achieving a significant amount of code reuse. Yes, there was a lot -of initial similarity to the JSON and HTML controllers, but they diverged over time. At the same time, we didn't +of initial similarity to the JSON and HTML controllers, but they diverged over time. + +At the same time, we didn't duplicate our Model logic: both controllers remained relatively "thin" and delegated out to our Model object to do most of the work. From 3da8cfbe1038b40d04ed2d222c5c10ea2d105cf6 Mon Sep 17 00:00:00 2001 From: Carson Gross Date: Wed, 17 Jan 2024 10:00:45 -0700 Subject: [PATCH 07/16] mvc --- www/content/essays/mvc.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/content/essays/mvc.md b/www/content/essays/mvc.md index 3b4498a5..818f4532 100644 --- a/www/content/essays/mvc.md +++ b/www/content/essays/mvc.md @@ -1,7 +1,7 @@ +++ title = "Model/View/Controller (MVC)" -date = 2023-01-16 -updated = 2023-01-16 +date = 2024-01-16 +updated = 2024-01-16 [taxonomies] author = ["Carson Gross"] tag = ["posts"] From 125fc79d4749b1a710b135905c77bd4daaddec3a Mon Sep 17 00:00:00 2001 From: Hawk Ticehurst <39639992+hawkticehurst@users.noreply.github.com> Date: Wed, 17 Jan 2024 09:44:11 -0800 Subject: [PATCH 08/16] [Website]: Fix cut off list items in demo-server-info panel (#2190) Fix cut off list items in demo-server-info panel --- www/templates/shortcodes/demoenv.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/www/templates/shortcodes/demoenv.html b/www/templates/shortcodes/demoenv.html index 8995b06d..de15050a 100644 --- a/www/templates/shortcodes/demoenv.html +++ b/www/templates/shortcodes/demoenv.html @@ -25,6 +25,10 @@ vertical-align: top } + #demo-activity ol li { + list-style-position: inside; + } + #demo-canvas { margin-bottom: 500px; padding-top: 12px; From c44101e2cbf340a380e143d74be39499d5e62891 Mon Sep 17 00:00:00 2001 From: Hawk Ticehurst <39639992+hawkticehurst@users.noreply.github.com> Date: Wed, 17 Jan 2024 09:45:23 -0800 Subject: [PATCH 09/16] [Website]: Fix demo-server-info panel rendering issue (#2188) Add z-index to demo server info panel so it is rendered above example page content --- www/templates/shortcodes/demoenv.html | 1 + 1 file changed, 1 insertion(+) diff --git a/www/templates/shortcodes/demoenv.html b/www/templates/shortcodes/demoenv.html index de15050a..a2cdf4ae 100644 --- a/www/templates/shortcodes/demoenv.html +++ b/www/templates/shortcodes/demoenv.html @@ -11,6 +11,7 @@ border-top: 2px solid gray; overflow: hide; margin: 0px; + z-index: 1; } #demo-server-info.show { max-height: 45vh; From 5d9c265eb4d35e507a143b34d2ba511a27dcef5c Mon Sep 17 00:00:00 2001 From: Carson Gross Date: Wed, 17 Jan 2024 12:19:25 -0700 Subject: [PATCH 10/16] mvc --- www/content/essays/mvc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/content/essays/mvc.md b/www/content/essays/mvc.md index 818f4532..47b06cf6 100644 --- a/www/content/essays/mvc.md +++ b/www/content/essays/mvc.md @@ -20,7 +20,7 @@ disentangle your "churny" web application API endpoints that return HTML from yo stable, regular & expressive JSON Data API. In looking back at conversations I've had around this idea with people, I think that I have been assuming familiarity -with a pattern that, perhaps, many people are as deeply familiar with as I am: the +with a pattern that many people are not as familiar with as I am: the [Model/View/Controller](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) (MVC) pattern. From 5aa0ec7e27c0dc282dd728886a77c0e321d3ca67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20Sala=C3=BCn?= <1910607+yansal@users.noreply.github.com> Date: Sat, 20 Jan 2024 00:30:37 +0100 Subject: [PATCH 11/16] Fix typos in mvc.md (#2208) --- www/content/essays/mvc.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/content/essays/mvc.md b/www/content/essays/mvc.md index 47b06cf6..7ab39f43 100644 --- a/www/content/essays/mvc.md +++ b/www/content/essays/mvc.md @@ -87,7 +87,7 @@ def contacts(): return render_template("index.html", contacts=contacts) ``` -Here I'm using [Python](https://www.python.org/) and Flask (https://flask.palletsprojects.com/en/3.0.x/), since I use +Here I'm using [Python](https://www.python.org/) and [Flask](https://flask.palletsprojects.com/en/3.0.x/), since I use those in my [Hypermedia Systems](https://hypermedia.systems/) book. Here you can see that the controller is very "thin": it simply looks up contacts via the `Contact` Model object, passing @@ -187,7 +187,7 @@ most of the work. Our two APIs are decoupled, while our domain logic remains centralized. -(Note that this also gets at why [why I tend not to use content negotiation](@/essays/why-tend-not-to-use-content-negotiation.md) and return HTML & JSON from the same endpoint.) +(Note that this also gets at [why I tend not to use content negotiation](@/essays/why-tend-not-to-use-content-negotiation.md) and return HTML & JSON from the same endpoint.) ## MVC Frameworks From 3ed0561f59c51f164e3e847645a91ecb3d9e696e Mon Sep 17 00:00:00 2001 From: Carson Gross Date: Sun, 21 Jan 2024 07:58:00 -0700 Subject: [PATCH 12/16] update complexity essay --- www/content/essays/complexity-budget.md | 78 +++++++++++++++-------- www/themes/htmx-theme/static/css/site.css | 8 +++ 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/www/content/essays/complexity-budget.md b/www/content/essays/complexity-budget.md index bc1e5f50..6010c4c9 100644 --- a/www/content/essays/complexity-budget.md +++ b/www/content/essays/complexity-budget.md @@ -1,44 +1,65 @@ +++ title = "Complexity Budget" date = 2020-10-29 -updated = 2022-02-06 +updated = 2024-01-21 [taxonomies] author = ["Carson Gross"] tag = ["posts"] +++ -Every application involves managing a complexity budget. A complexity budget can be defined as: +Every software project involves managing a complexity budget. + +A complexity budget can be defined as: > An explicit or implicit allocation of complexity across the entire application "Complexity" here is defined subjectively (rather than [formally](https://en.wikipedia.org/wiki/Programming_complexity)) -and in [Stewartian Terms](https://en.wikipedia.org/wiki/I_know_it_when_I_see_it): "I know it when I see it." Or, more -specifically to software development, "I know it when I *feel* it." +and in [Stewartian Terms](https://en.wikipedia.org/wiki/I_know_it_when_I_see_it): "I know it when I see it." -One of the primary jobs of an application architect is to manage a complexity budget: +Or, more specifically to software development: "I know it when I *feel* it." + +One of the primary jobs of an application architect is to manage a projects complexity budget: * Decide if a given feature is "worth it" * Decide if a given implementation is "worth it" -* Add in appropriate system boundaries to limit complexity between component -* Etc. +* Add appropriate system boundaries to limit complexity between components +* And so on -Note that attempting to address complexity can, in fact, add more complexity. A good example of this, from experience -is [OSGi](https://en.wikipedia.org/wiki/OSGi), which when applied to an application I was working on, made things -*far more complex*, grinding development to a halt. (This is not to say OSGi is universally bad, just that in this -case, rather than boosting developer productivity, it effectively ended it.) +An infuriating aspect of complexity is that that attempts to address it can, in fact, add more complexity. -A good software architect is someone who manages their software budget effectively, either explicitly or implicitly +A good example of this from experience was when a company I worked at added [OSGi](https://en.wikipedia.org/wiki/OSGi) to the system to manage the +increasing complexity of the project. It seemed like a reasonable approach, +it offered a sophisticated [module](https://www.osgi.org/resources/what-is-osgi/) system, +it was recommended by a newly hired architect, and it even says on the "What is OSGI page": + +> OSGi significantly reduces complexity in almost all aspects of development: code is easier to write and test, reuse is +> increased, build systems become significantly simpler, deployment is more manageable, bugs are detected early, and +> the runtime provides an enormous insight into what is running. + +What's not to like? + +Unfortunately, adding OSGi to that project effectively ground the entire project to a halt: it took a few of our best +engineers out of normal application development for over a year, and when they were done the codebase was even more +difficult to work with than when they started. Feature velocity, already teetering, collapsed. + +This is not to say OSGi is universally bad. But, in this case, rather than boosting our development teams productivity, +it effectively ended it. + +A good software architect is someone who manages the software budget of their project effectively, either explicitly or +implicitly. ## Complexity Growth -I assert, without evidence, that Stewartian Application Complexity grows roughly geometrically with the size of an -application. By proper factoring by experienced developers, this curve can be held down for quite some time, and this -is one major reason why many good developers are so much more productive than others. +My sense, admittedly without hard evidence, is that Stewartian Application Complexity grows roughly geometrically with +the size of an application. Proper [factoring](https://en.wikipedia.org/wiki/Decomposition_(computer_science)) by +experienced developers can hold this curve down for quite some time. -However, this doesn't change the fact that, somewhere out there, there is a Complexity Wall lurking and, if you aren't -careful you will run into it and grind development to a halt. I have had multiple experiences with this: one day, -inexplicably, development on a system that I was working on went from feeling "large, but manageable" to -"this is impossible to deal with". +However, this doesn't change the fact that, somewhere out there, there is a Complexity Wall lurking. + +And, if you aren't careful, you will run headlong into it and grind your development velocity to a halt. + +I have had multiple experiences with this: one day, inexplicably, development on a system that I was working on went +from feeling "large, but manageable" to "this is impossible to deal with". ## Spending Your Complexity Budget Wisely @@ -47,23 +68,24 @@ Here are some tools for managing your complexity budget: 1. Foremost: understanding that there *is* a complexity budget that needs to be managed 1. Focus your "complexity spend" on the areas where your application is adding value and/or differentiates itself 1. Saying "No" - probably the easiest, best and, also, hardest tool to use in your battle with complexity -1. Embracing [KISS](https://en.wikipedia.org/wiki/KISS_principle), even if it means admitting you are stupid (It's often very good for an organization if the senior developers can admit they are fallible) +1. Embracing [KISS](https://en.wikipedia.org/wiki/KISS_principle), even if it means admitting you are stupid (Note that it's often very good for an organization if the senior developers can admit they are fallible) 1. Proper factoring of components - this is an art: Too many components and your complexity explodes. Too few... same. 1. Choosing the proper balance of expressiveness and restrictions for a component -Unfortunately, experience shows that managing Stewartian Complexity is a subjective endeavor, and many talented and +Unfortunately, experience shows that managing Stewartian Complexity is a subjective endeavor and that many talented and experience developers will disagree on the proper course of action at a given decision point. -None the less, by making the concept of a complexity budget explicit, these conversations can be more productive and -ultimately lead to better software outcomes. +Nonetheless, by making the concept of a complexity budget explicit in your software project, these conversations can be +more productive and ultimately lead to better software outcomes. ## A Final Note -All mature applications are complex. +Almost all mature applications are complex. -Finding a new codebase "complex" is *not* an excuse for tearing everything -apart or aggressive refactoring. We must always bear in mind [Chesterton's Fence](https://fs.blog/2020/03/chestertons-fence/). +Finding a new codebase "complex" is *not* an excuse for tearing everything apart or aggressive refactoring. We must always bear in mind [Chesterton's Fence](https://fs.blog/2020/03/chestertons-fence/). If an application is functioning well (or even reasonably) then we should assume that the complexity budget was well -(or reasonably) managed. And we must also bear in mind that, with unfortunate frequency, attempts at addressing complexity -in existing, large applications often fail or, sadly, make things worse. +(or at least reasonably) managed. + +And we must always remember that, with unfortunate frequency, big attempts at addressing complexity in existing, large +applications often fail or, sadly, make things worse. diff --git a/www/themes/htmx-theme/static/css/site.css b/www/themes/htmx-theme/static/css/site.css index 8b82d3ba..5e78f447 100644 --- a/www/themes/htmx-theme/static/css/site.css +++ b/www/themes/htmx-theme/static/css/site.css @@ -178,6 +178,14 @@ ul li { padding: 4px; } +ol { + margin-left: 12px; +} + +ol li { + padding: 4px; +} + @media(max-width:45rem) { #nav { height: 0px; From f37191063cb7533a0f3d228a411778b38c6a2fdc Mon Sep 17 00:00:00 2001 From: Ali Afsharzadeh Date: Mon, 22 Jan 2024 03:45:52 +0330 Subject: [PATCH 13/16] chore: upgrade actions to v4 (#2211) --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad003516..a1dc09a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,9 +10,9 @@ jobs: test_suite: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: '15.x' - run: npm ci From 94ca5f5a360e1e491c1acc37fdfbf89ad353a8c6 Mon Sep 17 00:00:00 2001 From: MikeMoolenaar Date: Mon, 22 Jan 2024 01:33:36 +0100 Subject: [PATCH 14/16] Clarify hx-validate documentation (#2221) --- www/content/attributes/hx-validate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/content/attributes/hx-validate.md b/www/content/attributes/hx-validate.md index 2a92714a..355f7d47 100644 --- a/www/content/attributes/hx-validate.md +++ b/www/content/attributes/hx-validate.md @@ -5,7 +5,7 @@ title = "hx-validate" The `hx-validate` attribute will cause an element to validate itself by way of the [HTML5 Validation API](@/docs.md#validation) before it submits a request. -Form elements do this by default, but other elements do not. +Only `
` elements validate data by default, but other elements do not. Adding `hx-validate="true"` to ``, `