It depends. DNS is probably more proper solution, while haipin NAT is more like a dirty trick (well, the whole NAT is sort of dirty trick).
On the other hand, hairpin NAT is easier to maintain and in some ways works better. You can add one universal hairpin NAT rule and forget about it, knowing that it will work with any future forwarded port, with any hostname, or even without one with just numeric address.
DNS is better if you know there will be a lot of traffic between internal clients and internal server. Setting DNS record to internal address will allow them to communicate directly, instead of all traffic bouncing through router.
Universal hairpin NAT rule, what do you mean, something like this:
add chain=srcnat src-address=192.168.88.0/24 dst-address=192.168.88.238 protocol=tcp dst-port=80
out-interface=brigde-local action=masquerade
but not to use specified port?
It probably makes no real difference, but when I have public server, it feels more right to me when it sees the public address instead of router’s LAN address. But it’s really just a personal preference.
If you mean the variant with WAN address as source, I wrote it’s a personal preference. At the same time, it’s the choice between all connections from same LAN showing as from 1.2.3.4 (WAN address) or 192.168.88.1 (router’s LAN address), which does not really make much difference. In either case, you won’t be able to tell client 192.168.88.10 from 192.168.88.50, it’s common for all hairpin NAT variants.
If you care about telling internal clients from each other, you need to play with DNS.
I think your local PCs may talk to your local server directly. It’s the whole point of hairpin NAT to change the source address of LAN → LAN (more precisely LAN → Own_WAN_Public_IP → Same_LAN) connections. As described nicely in wiki with step by step what happens with packet.