If something like Ansible is too much, you could list the packages as a bash array in a file
pkgs=(
vim
bash-completion
...
)
Source the file
source pkgs.txt
Then install them
dnf install ${pkgs[@]}
This expands to dnf install vim bash-completion ...
As for listing the installed packages,
dnf repoquery --userinstalled --queryformat '%{name}\n'
The list includes all packages not installed as dependencies, so it's not quite perfect but might be close enough to what you need.
The array expansion workaround should work for other package managers too, as long as they take the list of packages as whitespace-separated arguments.