Πριν πολλά φεγγάρια ήθελα να πακετάρω μια web εφαρμογή μου, για να μπορεί να την πάρει κάποιος και να την εγκαταστήσει εύκολα. Μαζί με όλες τις… παραξενιές που απαιτούσε. Μου λέει ένας φίλος “βάλε Docker να γουστάρεις“.
Πάω κι εγώ να βάλω Docker, χωρίς να το ψάξω και πολύ, με την αίσθηση ότι είναι κάτι σαν Virtual Machine. Αυτό ήξερα, αυτό εμπιστευόμουν. Φυσικά δεν έβγαλα άκρη, αφού πήγα κι εγώ να στήσω Ubuntu με τα πάντα όλα μέσα. Χώσε και Apache, χώσε και PHP, χώσε και mySQL, χώσε και το απαραίτητο third party software. Κάνω restart το container, άφαντα τα data μου.
Με κορόιδεψαν με αυτή τη σαχλαμάρα, είπα. Τι περίεργα μαγικά είναι αυτά; Και άφησα το Docker κατά μέρους. Μέχρι που μετά από καιρό το έπιασα σωστά αυτή τη φορά. Διάβασα σχετική αρθρογραφία, είδα tutorials και μπήκα στο νόημα. Δεν ξανακοίταξα ποτέ πίσω. Και χάθηκα στο ηλιοβασίλεμα…
Τι είναι όμως το Docker; Το Docker όντως είναι κάτι σαν Virtual Machine. Αλλά δεν είναι. Μια Virtual Machine τρέχει μέσα σε ένα λειτουργικό (π.χ. Linux) και είναι η ίδια ένα ολόκληρο λειτουργικό. Είναι ένα ολόκληρο λειτουργικό, μέσα σε ένα ολόκληρο λειτουργικό. Και όλοι μαζί καταναλώνουν resources σαν να μην υπάρχει αύριο.
Ένα Docker container όμως από την άλλη, χρησιμοποιεί το host λειτουργικό. Χρησιμοποιεί τον πυρήνα του host. Δεν έχει δικό του πυρήνα. Με αποτέλεσμα να είναι πολύ ελαφρύ και να ξοδεύει μόνο τα αναγκαία resources, για να τρέξουν τα services που θέλει. Κάθε container είναι πλήρως απομονωμένο από το host λειτουργικό και από άλλα containers.
Η λογική του Docker, είναι ότι σηκώνουμε ένα container για κάθε service που θέλουμε. Θέλουμε Apache; Σηκώνουμε ένα container με Apache. Θέλουμε mySQL; Σηκώνουμε ένα container με mySQL. Και πάει λέγοντας. Φυσικά μπορούμε να τα χώσουμε όλα σαν ένα ενιαίο λειτουργικό σύστημα, αλλά δεν έχει κανένα νόημα όλο αυτό. Ένα γατάκι πεθαίνει κάθε φορά.
Μπορούμε επίσης να χρησιμοποιούμε διαφορετικές εκδόσεις ενός service σε ξεχωριστά containers. Θέλουμε PHP 5.6; Σηκώνουμε container με PHP 5.6. Θέλουμε με 7.4; Σηκώνουμε με 7.4. Αλλάζουμε πολύ εύκολα τις εκδόσεις και μπορούμε να δοκιμάζουμε την εφαρμογή μας σε διάφορα περιβάλλοντα.
Κάθε container δεν κρατάει δεδομένα μέσα του. Δηλαδή αν τρέχουμε ένα container με mySQL, τα δεδομένα της βάσης θα χαθούν αν κάνουμε restart το service. Γι’ αυτό τον λόγο κάνουμε αντιστοίχιση φακέλου του host μηχανήματος με φάκελο του container. π.χ. αντιστοιχούμε τον φάκελο /mysql/data από το host, στον φάκελο /var/lib/mysql εσωτερικά του container. Έτσι τώρα, τα δεδομένα της βάσης θα σώζονται στον φάκελο /mysql/data και θα συγχρονίζεται το container με αυτά.
Με το Docker μπορούμε να πακετάρουμε μια εφαρμογή μας, να μπορεί κάποιος να την χρησιμοποιήσει, είτε για development, είτε για κανονική χρήση. Το μόνο που χρειάζεται, είναι ένα αρχείο που περιγράφει ποια containers και πως θα σηκωθούν. Η εφαρμογή θα έχει στην διάθεση της όλα τα services που χρειάζεται, στην έκδοση που τα χρειάζεται, όπως τα χρειάζεται.
Προγραμματιστές που δουλεύουν ταυτόχρονα μια εφαρμογή, μπορούν να έχουν όλοι πανομοιότυπο development περιβάλλον, ανεξάρτητα σε πιο host μηχάνημα δουλεύουν. Ακόμη και σε διαφορετικά λειτουργικά (Linux, Mac OS, Windows)
Ενώ και το τελικό deploy της εφαρμογής σε κάποιον server μπορεί να γίνει πανεύκολα, σηκώνοντας κι εκεί όλα τα Docker containers που χρειάζονται.
Το Docker είναι σήμερα από τα βασικότερα εργαλεία ενός προγραμματιστή. Πραγματικά όποιος αρχίσει να το χρησιμοποιεί, απορεί μετά πως στο διάολο δούλευε χωρίς αυτό τόσα χρόνια. Απλοποιεί τόσο πολύ διάφορες καταστάσεις και διαδικασίες, που είναι μαζοχισμός να μην το χρησιμοποιείς.
Κι αν εγώ δεν σας τα εξήγησα ωραία, έχετε μια δεύτερη ευκαιρία. Ο τύπος στο παρακάτω βίντεο τα εξηγεί πολύ πιο ωραία (σαν τον Αλέφαντο). Νομίζω είναι το καλύτερο βίντεο που έχω δει για εισαγωγή στο Docker.
Και τώρα, ένα… ελαφρύ θεματάκι.
Το αν βάζουμε, και πόσο πυκνά, comments στον κώδικα μας, είναι μια διαρκής συζήτηση (πολλές φορές και αστεία). Μπορεί να αλλάζουμε στυλ συνεχώς. Ο γενικός κανόνας είναι να προσπαθούμε ο κώδικας μας να είναι self documented. Μικρές μέθοδοι, που κάνουν απλά πράγματα και ονόματα μεθόδων/μεταβλητών που περιγράφουν πολύ καλά τον ρόλο τους.
Παρ’ όλα αυτά, με διάφορους τρόπους, τα comments έχουν πάντα θέση στον κώδικα μας. Στην παρακάτω ομιλία περιγράφονται οι κακές περιπτώσεις και οι καλές περιπτώσεις.
Η ομιλία είναι από την Sarah Drasner (developer στην Netlify και Vue core team member) κι έγινε στο JSConf, στην Χαβάη (5-6 Φεβρουαρίου, 2020).
Προσωπικά, κάνω (ή έκανα στο παρελθόν) σχεδόν όλα όσα αναφέρονται στο video. Και τα κακά και τα καλά… 🙂
Εντάξει. Είναι λίγο μπερδεμένος ο τίτλος. Να εξηγήσω λίγο περισσότερο.
Ας πούμε ότι γράφουμε μια εφαρμογή σε Laravel, αλλά υπάρχουν κάποια κομμάτια του frontend, μέσα στα blade views μας, που χρησιμοποιούμε και λίγο Vue.
Για παράδειγμα, σε ένα view που κάνουμε post ένα άρθρο. Σε ένα σημείο μπορεί να θέλουμε να περνάμε tags, με έναν πιο διαδραστικό και άμεσο τρόπο, χρησιμοποιώντας Vue. Δηλαδή προσθέτουμε tag κι αυτό εισάγεται άμεσα στην βάση δεδομένων με ένα API call.
Το API call αυτό θα κάνει φυσικά ένα POST. Ας πούμε ότι ο κώδικας μας κάνει περίπου κάτι τέτοιο:
insertTag(e)
{
let myData = {
name: this.tag
}
axios.post('/api/tag', myData)
.then(response => {
this.tags.push({
id: response.data.id,
name: response.data.name
})
this.tag = ''
})
.catch(e => console.log(e))
}
Όπως βλέπουμε, πρέπει να γίνει κλήση στο /api/tag. Όμως καταλαβαίνουμε ότι POST σε αυτό το route δεν πρέπει να μπορεί να κάνει οποιοσδήποτε, αλλά κάποιος authenticated χρήστης.
To Laravel έχει ήδη έναν μηχανισμό. Στο αρχείο config/auth.php, υπάρχει ένας API guard που υλοποιεί έναν token driver. O driver αυτός ελέγχει για API token στα εισερχόμενα requests και ελέγχει αν αυτό ανήκει στον συγκεκριμένο χρήστη.
Παρ’ όλα αυτά, εμείς πρέπει να κάνουμε και μερικά ακόμη πράγματα.
Καταρχήν πρέπει να προσθέσουμε το πεδίο api_token στο migration του table users. π.χ.
$table->string('api_token', 80)->after('password')
->unique()
->nullable()
->default(null);
Πρέπει κάθε φορά που δημιουργούμε ή κάνουμε update έναν χρήστη να δημιουργείται και το api_token για τον συγκεκριμένο χρήστη. π.χ. στο αρχείο Http/Controllers/Auth/RegisterController.php που γίνεται register ο νέος χρήστης, μπορούμε να προσθέσουμε κάτι τέτοιο, στην μέθοδο create:
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'api_token' => Str::random(80)
]);
Αντίστοιχα στο UserFactory.php, αν χρησιμοποιούμε, προσθέτουμε ένα αντίστοιχο πεδίο
'api_token' => Str::random(80)
Και φυσικά όπου αλλού στον κώδικα μας μπορεί να χρειαστεί να δημιουργηθεί ή να γίνει update ο χρήστης.
Ας πάμε τώρα στην τελική υλοποίηση. Προσθέτουμε στο routes/api.php, κάτω από το middleware auth:api, όποια API routes χρειάζονται authentication. π.χ.
Route::group(['middleware' => 'auth:api'], function() {
Route::post('tag', 'AdminTagsController@store');
});
Το api_token θα πρέπει να το περνάμε στο blade view, όπου θα χρησιμοποιηθεί για να γίνει το API call. Πάμε π.χ. στον αντίστοιχο controller, στην μέθοδο που κάνει create την φόρμα για το post άρθρου. Προσθέτουμε κάτι τέτοιο:
public function create()
{
$user_id = Auth::id();
$userApiToken = Auth::user()->api_token;
return view('admin.posts.create', compact('user_id', 'userApiToken'));
}
Χρησιμοποιούμε δηλαδή το Auth::user()->api_token για να πάρουμε το api_token του χρήστη. Αυτό το περνάμε στο view που θα το χρειαστούμε.
Κάπου μέσα στο blade view θα πρέπει να μπει ο παρακάτω κώδικας, για να περνάει στην javascript το api_token.
<script>
let LaravelAuth = @json([
'apiToken' => $userApiToken ?? null,
]);
</script>
Τέλος, στο αρχείο resources/assets/js/bootstrap.js, θα πρέπει να περνάμε το token στα headers του Axios. Θα πρέπει να έχει δηλαδή κάτι τέτοιο:
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
if(typeof LaravelAuth !== 'undefined') {
window.axios.defaults.headers.common['Authorization'] = 'Bearer ' + LaravelAuth.apiToken;
}
Αυτό ήταν. Από εδώ και πέρα στα συγκεκριμένα routes που βάζουμε κάτω από το middleware auth.api, θα γίνεται έλεγχος για authentication.
Περισσότερα μπορείτε να βρείτε και στο documentation του Laravel. Παίζει όλη η μέθοδος να έχει αλλάξει με το Sanctum. Δεν το έχω ψάξει ακόμη όμως με αυτό και προς το παρόν στις εφαρμογές μου χρησιμοποιώ την παραπάνω μέθοδο.