There's a Delphi 12.1 patch and no-one told me!
2024-09-19 20:09 12_1_patch [permalink]
Well, "Wok" did actually, thank you "Wok". But I didn't notice it on the several Delphi-themed RSS feeds I have on my reader! But then again, I'm always incredibly behind on unread items, since I've already got about 600 feeds...
Clean Code: Big Chunks of Code
2024-08-15 11:35 ccbcc [permalink]
→ Understanding Clean Code: Functions ⚡
Sorry, but I strongly disagree. If you write a function and have it be called exactly only once, I dare to doubt the reason of existence of said function. I consider the call itself to be in the way. (Even if it's only because of the stack frame in memory and the extra line in the stack trace.) You may be better of with a raw file include (ugly!) or having the function be inline;
. What I typically do is have a big block of comment above and below such a section with explanation what it does and what its relation to sections above and below is. (Using {$region}
comes in handy here, as they are collapsed by default and may also provide 'overview readability' like the list of calls in the article, but without the code itself living somehwere else...)
It looks like good advice to split code in a series of calls, it looks like you're able to read from the list of names of functions that are called in sequence what is going on, but experience teaches this typically goes on an evolution of its own in the further progression of the project, and never sits quite right, causing more trouble than avoiding any. Especially when the functions themselves grow to do more things than their initial description.
What I usually do spend personal discipline on is making sure that the sections are independent among eachother, except for a limited set of references to objects that are operated on, and listed as such in the preceding comment. This is something that would get enforced by splitting in separate functions, but something that should emerge if you already think criticaly about the construction of larger chunks of code. Newer Delphi versions introduced the option to have a var
section in a begin end;
block, which could help with this, but I haven't grown accustomed to this yet. (Partly because I still keep a number of project fit to build with Delphi 7, sorry. The difference in file size of exe's and dll's is just too big.)
One more thing: In most cases, when I have sequences of code (sometimes tens of lines), that have to happen in sequence for a big operation (that sometimes get to hundreds of lines), each section get's its own try finally
and/or try except
, so you clearly know where something happens when something (inevitably) goes wrong at run-time.
This is ofcourse specific to Delphi (Object Pascal) and your milage may vary with other programming languages and development environments. It goes to show that its heritage goes back to Pascal which was designed to be a (theoretical) developer's language, and goes further back than C and its extensive family of languages that were primarily designed to be an evolution over assembler just so to get code to be cross-platform* and thus eliminating specific assembler as much as possible. (*: "cross-platform" here is PDP-7, Burrouhgs, Cray and punch-card-crunching machines from IBM; though it came in handy in the new Windows/
2024-01-08 17:48 xxm510 [permalink]
A quick follow-up release. I've had more reports of xxmHttp*.exe being wrongly identified as a Trojan horse, so I did more work on the code around the LoadLibrary call. (A very big thank you to VirusTotal. But that still doesn't fully guarantee it won't get flagged again now or in the future.)The NT-service versions apparently were broken, so I had to fix that. (And found a few more optimizations along the way!) I've been test-running a few days now and all works smoothly again. So be sure to update in case you've also ran into problems, otherwise nothing functional changed compared to revision 500.
2024-01-05 23:43 txsvc [permalink]
tx "stand alone version" is now available as an NT-service, since I added the files for xxmConv for it. (If you're interested, it's called from here.)
The original tx "stand alone version" was intended as a show-case to see if tx could work for you (and/or your team?), so it would only bind to 'local loop addresses' ('127.0.0.1' for IPv4 and '::1' for IPv6, when available) for security.
But if you like it — and are not planning to do development on tx itself — this service version is what you need. You could install it on a server on the local network, so it's available to your entire team.
If you do want to tinker with tx itself, — it's an open source project after all — you're welcome to install xxm to host tx.xxl
you build from source. (Hmm, should add a file with the steps in detail how to do that...)
2023-12-29 20:24 xxm500 [permalink]
ATTENTION:
Executable files xxmHttp*.exe
may get misreported as malware of type Trojan horse ("Trojan:Win32/Bearfoos.A!ml" or "Trojan:Win32/Wacatac.B!ml" or others). Please configure exceptions where appropriate, or post false positive reports to your respective malware-detection-tool-vendor. Feel free to inform me of these cases. I have revised the code around the LoadLibraryW call as I suspect this is the most sensitive bit of code that the xxm handler may have in common with Trojon horse software, but I have no definitive means to be sure.
2023-11-09 20:40 xxm474 [permalink]
?x&y=1
as 'x'='','y'='1'
in query string and form data (was 'x&y'='1'
previously)This is a relatively small release, but the NTLM/Negotiate change is too important (for security!) to wait too long with. Also the project entry cache should provide a performance increase in almost all cases. (Strange that I haven't noticed this sooner that this was a weak point!) So, in case you have projects that use NTLM (and ContextString(csAuthUser)
) to reliably identify users, It's very, very, warmly advised to switch to "negotiate":true
(instead of "ntlm":true
), and all should work exactly the same (for longer, and more securely). I considered just using 'negotiate' behind the scenes when "ntlm":true
is set, but I deem this distinct enough to make a separate setting and I guess security is a thing we should all be actively vigilant for. So they're both there for now, and a future release could drop NTLM. (Or it could be entirely missing from 2.0...)
Plans for the next release are mainly clean-up, for example deprecating xxmLocal (R.I.P. I.E.), and xxmRun (yes, I once thought people would use xxm from a CD-ROM, register it on auto-run and have the local Internet Explorer serve dynamic web-pages from an xxm project that uses the content from the disc... What was I thinking!). xxmGecko was already deprecated (yes, I once thought people would 'enjoy' — for lack of a better word — URL's in the address bar that start with xxm://
... What was I thinking!). With those out of the way I can do some more work on the underlying project entry registry, and have the project on in a good position to leave it for a while and maybe get started on 2.0... We'll see.
Update: there was a "v1.2.7.474" first, but had a bug in TXxmProjectCacheJson.FindProject, which would mingle projects between eachother when hot-reloading xxm.json... Be sure to update if you are running this version.
2023-10-15 20:23 noob [permalink]
There are no bad questions, there are only bad answers... I hope this wasn't too long an answer, but I guess I have everything I could think of:
https://www.reddit.com/r/delphi/comments/178gy9g/comment/k509hm1/?context=3
2023-06-05 22:28 xxm466 [permalink]
It's been quite a while since I've done a release, and I regret that I wasn't able to offer more novelties with this release, but the fixes and improvements do make a solid release. By switching to Delphi Community Edition, I can now provide 32- and 64-bits editions. I have also included 32-bits binaries compiled with Delphi 7, for any case where you need to host (existing?) older *.xxl
files and need the exceptions handled. Newer Delphi versions have a binary incompatibility in the exception handling, which either results in exceptions not getting handled correctly, or a new access violation thrown instead by Delphi's internal exception handling. (If you prefer running mixed-delphi-version xxm-handlers and -project-binaries, consider exposing IXxmProjectEvents1
by the project object, and use HandleException
to output a suitable message.
Also be advised, if you're updating from v1.2.4 or before, Web.xxmp's will still get converted from XML to JSON. (See the v1.2.5 release notes.)
WinHttpWS.pas: connect to a websocket using winhttp.dll
2023-03-30 23:02 WinHttpWS [permalink]
I needed to fetch something from a WebSocket real quick, but the project didn't have anything like networking components included yet. So I decided a quick-and-easy-way to get to what I needed is using winhttp.dll... I share this hoping it may come in handy for anyone else...
2023-03-03 22:51 dirdiff646 [permalink]
→ DirDiff v2.0.5.646
There was a bug when diff-ing XML files. Incorrect characters were shown when (re-)indenting nested XML elements. I also upgraded the project to Delphi Community Edition version 10.4
Eszett: enter ß by typing Ctrl+Alt+S (AltGr+S)
2023-02-23 22:24 eszett [permalink]
When typing German on non-German keyboards, it's not entirely clear how to enter the "ß" character. There's charmap.exe and Win+"." but those are all pretty indirect.
Install eszett.exe to map the combination of Ctrl, Alt and "S" (or 'AltGr' if your keyboards has it, and "S") to enter a "ß" character.
(Source code available here)
2022-06-25 00:52 FreeAndNil [permalink]
→ Coding in Delphi and beyond: Delphi Debates: FreeAndNil
;TLDR: I agree.
I, for myself, have a pretty straight-forward rule to follow. In most cases 'lifetime management' of objects is pretty plain and normal: there's one single place in the code where an object comes into existence (the call to a constructor), and exactly one single place in the code where it is cleaned up. In that case there's no reason that I can think of to use anything else than the Free
method. Sometimes the constructor is called from another constructor, and you free from the partner destructor, in effect linking the lifetime of the object to this 'owner' object. Most other cases, you just need an instance of a class to do something with. It typically looks like this:
var
a:TThing;
begin
a:=TThing.Create;
try
//...
finally
a.Free;
end;
end;
Neat! One very (very) important thing to note here is that under no circumstance you're supposed to do anything with this object reference after that (other than assigning with a new constructor call etc.) Thanks to Delphi internals, you'll probably get an access violation, but you might just as well end up calling some code that has been put in the memory where the instance was, and has unintended consequences.
If, for any reason whatsoever, you need/want to write code that may or may not be assigned to a live instance of an object, you could and should use nil
. In the places in the code where it's not sure if there's an instance, be sure to check for a nil
value. Consider this snippet:
var
a:TThing;
begin
a:=nil;//counter warning
try
if //only in some cases
begin
a:=TThing.Create;
//...
end;
//...
if a<>nil then
begin
//use a for something
end;
//...
finally
if a<>nil then
begin
a.Free;
a:=nil;
end;
end;
end;
Seasoned Pascal-coders will have objections to this snippet, rightfully so, but the point I want to make that there's some extra steps taken here to 'keep it safe'. There's just this one thing that's not so easy to spot: a.Free;
could in theory throw an exception. If there's an except
clause below that, and it wants to get some data from a
, it won't work. a
isn't nil, but the constructor may have already taken down the object instance so far down it will no longer behave as expected. There's a way around that though, and you can see for yourself rightaway if you have a look for yourself at FreeAndNil
itself:
procedure FreeAndNil(var Obj);
var
Temp:TObject;
begin
Temp:=TObject(Obj);
Pointer(Obj):=nil;
Temp.Free;
end;
(There's some casting to/from pointer and TObject here, but that's to make sure it works in even the strangest cases. It basically sets the reference to nil
before calling Free
.) If you've seen that first without the context I described above, you might think 'what is going on here' or 'what is the fuss about'. So I hope I was able to bring some enlightenment. The best case would be if you've learnt nothing new here, and also agree with this rule:
If you have an abnormal object instance lifetime management situation and/or need to entertain instance references that may be nil
, use FreeAndNil
. In all other cases use plain Free
(and keep calls to constructors and destructors in perfect balance!)
Delphi 10: "Debug" has "Use debug .dcu's" on by default
2021-09-01 18:48 d10debugdcus [permalink]
Since the community edition, and since it includes the command line compiler, I've been persuaded to switch some hobby projects I was sticking to Delphi 7 for. I've enjoyed a few of the new features I've been missing on (Ctrl+Shift+F! F6!), but also have noticed a few strange quirks I'm not so content with. [WARNING: what follows is my own opinion, you have every right to disagree and it is not at all implied you're doing anything wrong by disagreeing] If you start a new project, you get a "Debug" and "Release" configuration in the project, but the "Debug" project has the "Use debug dcu's" option checked in the project options under Building > Delphi Compiler > Compiling > Debugging. I regret that because when you follow what code does using the debugger and pressing F7 repeatedly, and you're not using the debug .dcu's, you only get to see code from your project. Especially for beginners, I think it could be quite jarring to end up in the thick of VCL-code without a clear way how to get out of there. (Shift-F8? Sometimes but not always.) If you F7 on a TStringList's LoadFromFile, it shouldn't descend into that unless you really need to and know what you're searching for down there... But that's just my opinion. Another thing that struck me is that local variables don't get zeroed by the 64-bits compiler, but I'm not sure if there's a switch or setting that controls that?
Ask HN: What is the SQLite of nosql databases?
2021-06-13 10:33 hnsqlitejson [permalink]
→ Ask HN: What is the SQLite of nosql databases?
Though I'm very much in agreement with:
The SQLite of NoSQL is still SQLite: https://www.sqlite.org/json1.html
there are situations where you could just use a single JSON object to store everything?
If your JSON implementation of choice does it correctly, and I attempted to get this right in my implementation, looking up a key from the list of key-value-pair should use a method that wastes no time and gets you to the value you want as swiftly as possible, no matter how many keys you need on the first level.
Then again, if you decide to use SQLite, and really need almost nothing else, I've created this one...
Do n.GetFirstChild=nil instead of n.Count=0
2021-03-21 17:25 nodecount0 [permalink]
I wish I knew this one sooner! If you've worked with TTreeView before, and know about TTreeNode's HasChildren value, you need to check with OnExpanding, if you need to load the children or not. So I would typically have something like this:
if Node.HasChildren and (Node.Count=0) then //load children nodes
Only now after all these years I happen to come past this in the Vcl.ComCtrls.pas unit:
function TTreeNode.GetCount: Integer;
var
Node: TTreeNode;
begin
Result := 0;
Node := GetFirstChild;
while Node <> nil do
begin
Inc(Result);
Node := Node.GetNextChild(Node);
end;
end;
So I've been throwing away performance all this time! Because my 'are the children loaded yet' check was taking more time if they were and there were a lot of them, expanding nodes was slower. And I never noticed!
So it's a good thing I know this, and now I can just write:
if Node.HasChildren and (Node.GetFirstChild=nil) then //load children
205B random strings and no 'Delphi'
2021-02-28 18:48 randomdelphi [permalink]
→ Delphi-PRAXiS: Can Delphi randomize string 'Delphi'?
Ah, that takes me back. A while ago at work I got someone baffled by this statement: It is said that a thousand monkeys banging away on keyboards could at some moment in infinite time produce the complete work of Shakespeare (and that the combined internet forums are a living counter-proof). Now if you search online you can find an XML download of the combined works of Shakespeare, so it's not hard to find the relative occurance of each letter of the alphabet. One can guess this will have rougly the same values per letter as the total of the English language. So, then, if you take 'random' by its definition, and the monkey's produce text at 1/26 probability for each letter in the alphabet, therefore they'll never reach a point in time where they (re)produce the works Shakespeare.
Now, I'm not a real philosopher, or a statistician, so my thesis could be complete fiction and based on nothing, but sometimes you really got to take a lesson from practice. In theory, in infinite time, it's ofcourse possible that something really really inherently possible could emerge out of a random system, but there are characteristigs to anything random, and there are exponential things at play that soon but experimantal set-ups like with the link above, that they probably would produce the expected outcome in a time-span that exceeds the number of years we've got left before the sun sheds its outer layers and devours the earth, complete with a set of silicon-based machines churning away at putting a series of random characters in sequence and comparing them of some neat stories of a long gone English playwright.
2021-02-25 20:12 diytotp [permalink]
Recently, I've got a few things asking to enable two-factor-authentication, and I started using the Google Authenticator app.
I kind of like it. It's a simple enough app, there's a shared secret involved, but it gets pretty close to being airgapped and perfectly forward secure and all of those things. So I got thinking... What would it take to start using it for myself, in those little software things I create now and then...
Is there black magic or stick whittling involved? Nah, a little searching around, and it all appears to be cleanly described in RFC's 6287 and 4226... There has to be a warning here about not rolling your own crypto, but the world of hashing and encrypting really is interesting! I did SHA1 and HMAC before, and Unix' time apparently is UTC... So all you need* is the correct format of URL to put into a QR-code to load up a new key in the app. Then you can use this code to generate the 'current' pass-code for the secret:
github.com/stijnsanders/tools/.../crypto/totp.pas
*: and apparently base32-encoding, HashUtils was missing that...
Lobsters on Delphi: the good and the bad
2021-02-18 00:20 lobsters [permalink]
→ Lobsters: 26 Years... of Delphi
Aaw, look at that, the good and the bad on a single page.
Good to know: don't do except on e:Exception do ... raise e;
2020-12-28 13:29 dontraisee [permalink]
Good to know. At work, I inherited a smaller project. It started firing access violation errors reading from 00000000 (that tells you there's a nil pointer involved somewhere) at some address that didn't put me somewhere in the source code when I did Search > Find address in the IDE...
I was lucky I got the program running in a debugger when this happens, and the root cause was something completely different. (An SQL unique index constraint violation if you're really interested.) I got that fixed soon enough, but that didn't explain where the access violation comes from.
I followed the exception with the debugger, and before the exception was handled by anything that outputs like a logger or an error display, sure enough an access violation happend, but the debugger didn't show me anywhere else in the source code than it was already debugging, strange.
It took me a long hard look at the code before I spotted it, but with some luck you'll be able to spot what's wrong faster. The original author had the strange habit of writing exception handlers like this:
try
//important stuff here
except
on e:Exception do
begin
//stuff to do in case of an exception here, (e.g. transaction rollback)
raise e;
end;
end;
And they were all over the place. Nothing bad about that per sé, with raising the exception again, you can centralise the actual exception handling on the 'highest level' where all the calls are made. But while you're able to raise a specific exception if you create a new one*, apparently there's a difference between raise e;
and raise;
at least in this Delphi version I was on. Delphi handles the destruction of exception objects for you, and apparently that throws something off and causes the nil access violation somewhere deep inside...
*: Fun fact: Did you know that the Delphi runtime system creates an EOutOfMemory exception instance at the start of your programs, just to have it ready when it actually runs out of memory anytime later? Forgot where I read about that though...
2020-12-18 16:26 invoicetemplates [permalink]
→ https://stackoverflow.com/questions/65348138/quickreport-on-delphi-sydney-10-4-1
Oh my, I didn't know. I guess we'll see this more and more with one-person open-source projects, that people stop with the project for all kinds of reasons.
But about reports, I've made this really elegant thing at work to script the design of a report with just a few basic commands (line, text, image, matrix, block, repeat...) that allows you to write what the report should look like, and make lots of small alterations later. If you really need your reports just right, I've always found a graphical designer to introduce minor artefacts that you sometimes need to work around...
I know there are tons of similar things out there, like TeX and PostScript, and even HTML or MetaFiles, but it was grown out of neccessity and suffered "dogfooding" from the very start, which shows in its design and execution. And thanks to SynPDF, (or "Print to PDF" for that matter) you can just exports PDF's with it as well. So I really should take some time and re-do it on my own time and open source it... If I can make it even better and cleaner, I might introduce the open-source version as a replacement at work! (... Oh-oh!) (... or was it this one?)
2020-06-05 17:23 cppwebdev [permalink]
→ Quora: Why is C++ not used in web development?
For what it's worth, in creating xxm it feels like I'm trying to create this exact thing except for with the Object Pascal language. Yes, you can do HTTP all by yourself, you can do an ISAPI extension DLL or a Apache httpd module, but you'll still get a strange hybrid between a server service and a web application that has nothing like a platform you can depend on to do the heavy lifting. And, if I'm permitted to speak frankly, in C++ this would be ugly! And probably would need a lot of code to make even the basic things happen. Too bad (Object) Pascal has been called verbose, if you know what you're doing you can write the logic you need in concise readable syntax.
Still what I'm finding in trying to get people to take a look at xxm, they either are unable to disregard the visual RAD form-designer style programming like I do, and don't get that xxm in it's current for is much more like early-days PHP but with the Delphi compiler instead of the script interpreter server-side; or they are fixed in thinking 'the web + Delphi' is all about a data-layer, doomed to only serve plain CRUD requests to and from a front-end layer, and never talk to the user's browser directly. Please! A big strong no on both accounts. Let me explain.
I've always seen — pretty much since FrontPage and DreamWeaver — that if you have a visual designer to manage what to go on a HTML page, you get really ugly code. It'll look the way you want, but a lot of decisions have been made for you. Some with negative consequences for you down the road. And the underlying code is strange and ugly, unneccessarily complex for your website-visitors' browser to work with. I guess modern front-end web-devs must have known this also as I've seen a regression towards working on big chunks of raw coding the last decade. Yes, font-ends are hacking away in CSS and HTML, and not with their bare hands, all kinds of CSS pro-processors and template engines do the heavy lifting behind. So if you know what you're doing, you can have this as well, in a Delphi project. I don't need a form designer, I make the page-builder first with dummy data, and run it in the browser. Don't forget, hitting F5 in the browser to an xxm website running over a development-handler, fires up the Delphi compiler there and then. Edit→Save→Refresh→Repeat
Then there's the other thing. If you start a conversation about webservers and Delphi, bam there's DataSnap. Strange. Is it because I'm strongly dys-convinced about ORM's? (Reminder to self: still have to write that grand essay about what's bad about ORM's.) Yeah sure, if you have things that use RTTI to serialize your data-objects in some way, you can easily use one of the available options to serve it over some web-server and bam you can call it REST and get away with it. But this is a completely different thing than having a full blown web-application serve from something you created! Complete with images and stylesheets. And yes you can have both🤯 from the same web-server-service🤯. I've had people walk away, unable to believe me. It still feels like it's a case of opening your mind to be able to see it.
Anyway, sorry about the rant. I'm getting tired of trying to find the people that combine real web dev with Delphi for the server side. Yes there are a lot of new things that compile (Object) Pascal to JavaScript, but that's another story. That's nice for the front-end. But if you dig deeper, or want to reach the people that you can't offload your gargantuan client-side workhorse to, please think about xxm. Give it a try, see if you can make it work. If you want an example of what I'm talking about, take a look at feeder or tx.
2020-05-27 10:30 primescheck [permalink]
→ J: Doc/Articles/Play104: The 10,000,000,000th Prime Number
http://yoy.be/home/primes/?a=2&x=10000000000&t=1&n=1 p(455052511)=10000000019 is actually the first prime past 10^10, but that does mean my primes thingy found the 455052511 primes lower than that.
http://yoy.be/home/primes/?a=1&x=10000000000&t=1&n=1 p(10000000000)=252097800629
2020-03-16 20:29 nottx [permalink]
→ Microsoft Teams goes down just as Europe logs on to work remotely — Verge
Tx wasn't down, especially the instance you host yourself.
About x in ['0'..'9'] and CharInSet
2019-11-05 22:42 anticharinset [permalink]
I'm still in the (slow) process of letting go of Delphi 7 and embracing the modern version(s) of Delphi, which to be honest was only possible since there's a community edition (that includes the stand-alone compiler(s)). I was in doubt if I would move over to the WideChar
based default string
, but since Windows internally switched long ago — and you get emoji-support for free — I thought I should switch also and haven't even tried to override the new default and switch string
to be AnsiString
just to keep things the old way. It works remarkably well. Any code that assumes the length of the string is also the number of bytes to move around needs to change, but that's a good opportunity to change them over to operations that use encoding support. With PChar
and char
that also changes mapping (to PWideChar
and WideChar
respectively) any coding use those just keeps working. Except in code like x in ['0'..'9']
which I apparently use a lot. It works except when compiling it throws this warning:
Warning: W1050 WideChar reduced to byte char in set expressions. Consider using 'CharInSet' function in 'SysUtils' unit.
At first I was baffled. Why is it reduced to a byte char? I know set expressions trigger all kinds of compiler optimizations (that's why I like to use them so much), but has someone been too lazy to make them happen with 16-bit values as well? I guess we're talking assembler jump-lists here, so it probably matters if you can limit them to 256 positions, and 65536 positions would add too much of dead chunks of data into your compiled code... But still. x
is WideChar
, any '0'
-style literals should default to WideChar
, shouldn't they? So I regret there was no preference to support code re-use here, and CharInSet which appears to be just a wrapper around the in
syntax, strange.
I don't like warnings and hints. I typically avoid them at all cost, treating them as compile errors. It helps to keep your code clean, and avoids trouble down the line. (But if you're really into avoiding trouble down the line, you should do decent static code analysis...) I'm kind of strict on myself that way. So I need to know what's the best way to avoid this warning. In someone else's code I saw x in [WideChar('A')..WideChar('Z')]
but that doesn't appear to suppress this warning. So I settled on AnsiChar(x) in ['0'..'9','A'..'F']
which probably calls a Unicode conversion routine, but I don't care enough. Old Delphi understands it, new Delphi no longer throws an error, and as far as I know the compiler optimizations still kick in and I haven't seen any performance loss over code like this. So I can sleep again.
Unless someone throws me a much better alternative and I won't sleep for a week because it feels like I should have known that all along...
Databases inside Delphi Ecosystem
2019-10-08 22:40 lgdbdelphi [permalink]
→ landgraf.dev: Databases inside Delphi Ecosystem: Webinar
Ugh, ORM? No thank you. I still need to write the full and extensive treatise about why exactly I prefer not to use ORM's, but every time I see something like "Databases in Delphi", and see the content rapidly veers toward heavy pre-moulded data-layers that bind you to things like ORM and REST API's, I regret so many people fall into this trap. Beginners think they're keeping things easy and scalable, but all hit the same wall later up when performance and complexity is letting them down hard, in part because of these basic choices made up front. I think it's very important to educate newcomers about the alternatives. There are downsides to ORM and heavy data-layers that you should know about.
Which I'm planning to address in a specific page "Why I dislike ORM's". One day. When I'm ready. For now I'll just say I'm glad I've stayed clear of them. I've looked beyond what's typically put forward as the only alternative, and went behind any platform, to talk to SQLite, PostgreSQL and MongoDB directly, unhindered. You get something working relatively quickly. It allows me to tap their full potential and have them scale right along with the front-end as it needs to grow to handle a larger volume of requests. I'm not saying it's perfect. This kind of architecture also has its downsides, but it has one big dependency less into the mix.