Guide complet pour le développement d’un projet d’enchères en ligne avec ACCP 6.0

Implémentation du projet d’enchères en ligne

Le projet d’enchères en ligne est une plateforme interactive majeure dans le domaine du commerce électronique. Il offre aux utilisateurs la possibilité de participer à des ventes aux enchères tout en ouvrant de nouveaux canaux de vente pour les entreprises. Cette section détaille le processus complet de développement, depuis l’analyse des besoins initiaux jusqu’à la configuration de l’environnement, en plongeant au cœur de la mise en pratique.

Présentation du projet et analyse des besoins

Contexte et objectifs du projet

La création d’une plateforme d’enchères en ligne répond à la nécessité de numériser le modèle traditionnel des enchères. L’objectif est de lever les contraintes géographiques, d’améliorer l’efficacité des transactions et d’attirer un public plus large. Le projet vise à construire un système sécurisé, stable et facile à utiliser, prenant en charge des fonctionnalités essentielles telles que les enchères en temps réel et les offres automatiques, tout en gérant des scénarios à forte concurrence pour garantir une expérience utilisateur optimale.

Besoins fonctionnels et flux métier

Les principales fonctionnalités de la plateforme incluent l’inscription et la connexion des utilisateurs, l’affichage des produits, les enchères en direct, l’historique des offres et les enchères automatiques. La plateforme doit également prendre en charge des fonctions d’administration backend telles que la gestion des utilisateurs, la gestion des produits et l’analyse statistique des données. Le flux métier commence par l’inscription et la connexion de l’utilisateur, qui parcourt ensuite les produits, participe aux enchères, tandis que la plateforme met à jour les informations en temps réel et traite les transactions à la fin de l’enchère.

Les sections suivantes exploreront comment concevoir le système pour répondre à ces besoins, configurer l’environnement de développement nécessaire et utiliser des outils appropriés pour une gestion de projet efficace.

Maîtrise des compétences en développement front-end

Application des technologies front-end de base

Fondamentaux HTML/CSS/JavaScript

Les compétences de base en développement front-end sont essentielles pour créer des pages web modernes. HTML (HyperText Markup Language) constitue la structure du contenu web, définissant sa mise en page via des balises pour orgeniser le texte, les liens, les images et d’autres ressources. Par exemple :

<html>
<head>
    <title>Ma page web</title>
</head>
<body>
    <h1>Bienvenue sur ma page</h1>
    <p>Ceci est un paragraphe de texte.</p>
</body>
</html>

CSS (Cascading Style Sheets) gère le style et la mise en page de la page web. Il permet d’ajouter des designs aux éléments HTML, comme les couleurs, la disposition ou les polices. Par exemple :

body {
    background-color: #f8f9fa;
}

h1 {
    color: #212529;
    text-align: center;
}

JavaScript est le langage de programmation interactif du web, qui donne vie aux pages. Il permet aux pages de réagir aux entrées utilisateur, de traiter des données et de communiquer avec le serveur. Par exemple :

document.addEventListener('DOMContentLoaded', function() {
    alert('La page a été chargée avec succès !');
});

Maîtriser HTML/CSS/JavaScript est fondamental et crucial pour le développement front-end.

Principes et mise en œuvre du design responsive

Avec la prolifération des appareils mobiles, le design responsive est devenu essentiel. Il s’agit de concevoir des pages web capables de s’adapter à différentes tailles et résolutions d’écran pour offrir une expérience de navigation agréable. Cela repose sur des mises en page flexibles, des images adaptatives et l’utilisation de media queries.

Un exemple simple de design responsive avec CSS :

.container {
    width: 100%;
    padding-right: 15px;
    padding-left: 15px;
    margin-right: auto;
    margin-left: auto;
}

@media (min-width: 576px) {
    .container {
        max-width: 540px;
    }
}

@media (min-width: 768px) {
    .container {
        max-width: 720px;
    }
}

@media (min-width: 992px) {
    .container {
        max-width: 960px;
    }
}

@media (min-width: 1200px) {
    .container {
        max-width: 1140px;
    }
}

Grâce aux media queries (@media), les développeurs peuvent définir des règles de style pour différentes largeurs d’écran, assurant un affichage correct sur tous les appareils.

Utilisation des frameworks et bibliothèques front-end

Sélection du framework et considérations de performance

Les frameworks front-end modernes offrent des fonctionnalités riches pour construire rapidement des applications monopages (SPA) complexes. Les frameworks populaires incluent React, Vue.js et Angular. Le choix du framework doit tenir compte des besoins du projet, des compétences de l’équipe, de l’écosystème et des performances.

Par exemple, React peut être choisi pour son modèle de développement basé sur des composants puissants et son DOM virtuel (Virtual DOM) efficace, tandis que Vue.js est apprécié pour sa simplicité d’utilisation et sa courbe d’apprentissage rapide. Angular, avec sa solution complète et son support de TypeScript, convient aux applications d’entreprise de grande envergure.

Application des bibliothèques de composants d’interface utilisateur

Pour améliorer l’efficacité du développement et assurer la cohérence de l’interface, de nombreux frameworks front-end sont associés à des bibliothèques de composants UI. Par exemple, Ant Design pour React, Vuetify pour Vue.js et Material Design pour Angular.

Ces bibliothèques fournissent un ensemble de composants prédéfinis comme les boutons, les champs de saisie, les tableaux et les barres de navigation, ce qui réduit considérablement le travail de développement et garantit une interface esthétique et uniforme.

Exemple d’utilisation d’une bibliothèque de composants UI :

import { Button } from 'antd';

function MonComposant() {
    return <Button type="primary">Bouton principal</Button>;
}

En utilisant ces bibliothèques, les développeurs peuvent se concentrer sur la logique métier plutôt que sur les détails de l’interface.

Optimisation des performances et gestion de la compatibilité

Techniques d’optimisation du chargeement des pages

La vitesse de chargement des pages est un indicateur clé de l’expérience utilisateur. Les techniques d’optimisation incluent la minification du code, l’optimisation des images, le chargement différé (lazy loading) et l’utilisation de CDN (Content Delivery Network).

La minification du code peut être effectuée avec des outils comme UglifyJS ou Terser, qui suppriment les espaces blancs et les commentaires inutiles pour réduire la taille des fichiers. L’optimisation des images implique le choix du format (comme WebP) et la compression. Le chargement différé consiste à charger les éléments seulement lorsque l’utilisateur fait défiler la page jusqu’à eux.

Solutions pour la compatibilité des navigateurs

Les problèmes de compatibilité entre navigateurs sont un défi courant. Pour garantir le bon fonctionnement des pages sur différents navigateurs, on utilise des polyfills pour ajouter des fonctionnalités manquantes aux anciens navigateurs, ou des transpileurs pour convertir le code en une version compatible.

Par exemple, pour utiliser la méthode Array.prototype.includes dans un ancien navigateur qui ne la supporte pas, on peut utiliser Babel ou un polyfill :

if (!Array.prototype.includes) {
    Object.defineProperty(Array.prototype, 'includes', {
        value: function(searchElement, fromIndex) {
            if (this == null) {
                throw new TypeError('"this" est nul ou non défini');
            }
            var O = Object(this);
            var len = parseInt(O.length);
            if (len === 0) {
                return false;
            }
            var n = fromIndex | 0;
            if (n >= len) {
                return false;
            }
            var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
            while (k < len) {
                if (O[k] === searchElement) {
                    return true;
                }
                k++;
            }
            return false;
        }
    });
}

Ces solutions aident les développeurs à assurer une bonne compatibilité et des performances optimales sur différents navigateurs.

En résumé, maîtriser le développement front-end nécessite non seulement une bonne connaissance de HTML/CSS/JavaScript, mais aussi la capacité à utiliser des frameworks et des bibliothèques, ainsi qu’à optimiser les performances et à gérer les problèmes de compatibilité au fil du développement.

Maîtrise des compétences en développement back-end

Fondamentaux du développement back-end

Choix du langage serveur et configuration de l’environnement

Dans le développement back-end, le langage serveur constitue le cœur de la logique applicative. De nombreux langages existent, comme Java, Python, Ruby, PHP et Node.js. Le choix dépend des besoins du projet, des compétences de l’équipe et de l’écosystème. Par exemple, Java est prisé pour les applications d’entreprise grâce à son écosystème robuste, tandis que Python est apprécié pour sa syntaxe claire et ses capacités en science des données.

La configuration de l’environnement est une étape cruciale pour garantir la cohérence et la reproductibilité. Pour Python, on recommande souvent d’utiliser virtualenv pour créer un environnement isolé. Pour Java, il faut configurer le JDK et des outils de construction comme Maven ou Gradle. Les pratiques courantes incluent l’écriture de scripts de configuration ou l’utilisation de Docker pour standardiser l’environnement.

Opérations sur les bases de données et gestion des transactions

Les opérations sur les bases de données sont un autre pilier du développement back-end. Les systèmes de gestion de bases de données (SGBD) relationnels comme MySQL et PostgreSQL, ou non relationnels comme MongoDB et Cassandra, ont chacun leurs usages. La conception de la base de données implique la modélisation des données, l’optimisation des index, la performance des requêtes et la cohérence des données.

La gestion des transactions garantit l’atomicité, la cohérence, l’isolation et la durabilité (ACID) des opérations. Dans les applications web, les développeurs back-end doivent contrôler les transactions via la programmation pour assurer l’intégrité des données en cas de panne. Par exemple, dans le framework Spring de Java, l’annotation @Transactional simplifie la gestion des transactions.

Application avancée des frameworks back-end

Principes du framework MVC et mise en œuvre

Le modèle MVC (Modèle-Vue-Contrôleur) est un modèle de conception qui sépare l’application en trois composants principaux : le modèle (données), la vue (affichage) et le contrôleur (logique de traitement). L’adoption du modèle MVC améliore la maintenabilité et l’extensibilité du code.

Dans les projets réels, l’utilisation d’un framework MVC comme Spring MVC ou Ruby on Rails augmente considérablement l’efficacité du développement. Ces frameworks fournissent des conventions et des configurations prédéfinies, permettant aux développeurs de se concentrer sur la logique métier. Pour maîtriser ces frameworks, il faut comprendre comment leurs composants prnicipaux interagissent et comment mapper correctement la logique métier aux trois parties du MVC.

Conception et mise en œuvre de l’API RESTful

Avec la popularisation des services web, l’API RESTful est devenue une norme pour la conception d’interfaces serveur. REST (Representational State Transfer) met l’accent sur les communications sans état, en utilisant les verbes HTTP (GET, POST, PUT, DELETE) pour décrire les opérations, ce qui rend la conception simple et cohérente.

Pour concevoir une API RESTful, il faut suivre des principes clés : les ressources doivent être représentées par des noms, les URL doivent suivre un modèle uniforme, et les opérations sur les ressources doivent être implémentées via les verbes HTTP. Par exemple, obtenir la liste des utilisateurs avec GET /users et créer un nouvel utilisateur avec POST /users. En pratique, les développeurs back-end doivent apprendre à utiliser le routage et les contrôleurs fournis par le framework pour implémenter ces principes.

Sécurité back-end

Validation des entrées et encodage des sorties

La sécurité est un aspect crucial du développement back-end. La validation des entrées empêche les utilisateurs malveillants de soumettre des données non conformes qui pourraient nuire au système, comme les injections SQL. L’encodage des sorties prévient les attaques de type cross-site scripting (XSS) en encodant les données avant de les afficher dans le navigateur, afin qu’elles ne soient pas interprétées comme du code.

En pratique, l’utilisation de fonctions et de modules intégrés au framework peut améliorer l’efficacité et la fiabilité de la sécurité. Par exemple, dans Flask (Python), la méthode validate_on_submit peut valider les formulaires, et la bibliothèque markupsafe peut encoder les sorties de manière sécurisée. Les développeurs back-end doivent se familiariser avec ces fonctionnalités de sécurité propres à leur langage et framework pour réduire les risques.

Mécanismes d’authentification et d’autorisation

L’authentification vérifie l’identité d’un utilisateur, tandis que l’autorisation détermine si cet utilisateur a le droit d’effectuer une action spécifique. Leur mise en œuvre nécessite l’intégration de systèmes d’authentification utilisateur, de gestion de sessions et de stratégies de contrôle d’accès.

Pour concevoir des mécanismes sécurisés, les développeurs doivent maîtriser diverses technologies comme l’authentification par token, le protocole OAuth2.0 et les JWT (JSON Web Tokens). Cela implique de comprendre leur fonctionnement, leurs avantages et les bonnes pratiques. Par exemple, pour JWT, le serveur doit générer et vérifier les tokens, tandis que le client doit gérer leur stockage et leur transmission.

Exemple de code : authentification JWT avec Python et Flask

from flask import Flask, request, jsonify, make_response
from werkzeug.security import generate_password_hash, check_password_hash
import jwt
import datetime

app = Flask(__name__)
app.config['SECRET_KEY'] = 'votre_cle_secrete'

# Inscription utilisateur
@app.route('/register', methods=['POST'])
def register():
    data = request.get_json()
    mot_de_passe_hash = generate_password_hash(data['password'], method='sha256')
    # Logique de stockage de l'utilisateur (omise)
    return jsonify({'message': 'Utilisateur créé avec succès !'}), 201

# Connexion utilisateur
@app.route('/login', methods=['POST'])
def login():
    auth = request.authorization
    if not auth or not auth.username or not auth.password:
        return make_response('Vérification impossible', 401, {'WWW-Authenticate': 'Basic realm="Connexion requise !"'})
    # Logique de vérification du nom d'utilisateur et du mot de passe (omise)
    # Si la vérification réussit, générer un token pour le client
    token = jwt.encode({'public_id': utilisateur.public_id, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30)}, app.config['SECRET_KEY'])
    return jsonify({'token': token})

# Ressource nécessitant une authentification
@app.route('/resource', methods=['GET'])
def get_resource():
    token = request.headers.get('Authorization').split(" ")[1]
    try:
        data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"])
    except:
        return jsonify({'message': 'Token invalide'}), 401
    return jsonify({'data': 'Vous êtes connecté !'})

if __name__ == '__main__':
    app.run()

Dans ce code, l’endpoint /register gère l’inscription en hachant le mot de passe. L’endpoint /login vérifie les informations de connexion et génère un token JWT en cas de succès. L’endpoint /resource nécessite que le client fournisse le token dans l’en-tête de la requête ; le serveur vérifie sa validité avant d’autoriser l’accès. Notez que des considérations de sécurité supplémentaires (HTTPS, gestion de l’expiration des tokens, etc.) doivent être prises en compte dans un environnement de production.

Application des protocoles de communication réseau

Comprendre les bases de la communication réseau

La suite de protocoles TCP/IP et HTTP

La suite de protocoles TCP/IP (Transmission Control Protocol/Internet Protocol) est le fondement de la communication sur Internet. TCP assure une transmission fiable des données, tandis que IP achemine les paquets vers leur destination. Lorsque vous tapez une URL dans votre navigateur, cette suite de protocoles entre en jeu, encapsulant votre requête de la couche application à la couche réseau, jusqu’au serveur, puis renvoyant la réponse de la même manière.

HTTP (Hypertext Transfer Protocol) est un protocole de la couche application qui repose sur TCP/IP. Il permet de transférer des hypertextes du serveur web au navigateur local. HTTP est sans état : il ne conserve pas les informations des requêtes précédentes. Il utilise généralement le port 80, tandis que HTTPS utilise le port 443 et chiffre les communications via SSL/TLS.

WebSocket et les technologies de connexion persistante

WebSocket permet une communication bidirectionnelle sur une seule connexion TCP, permettant au client et au serveur d’envoyer des messages à tout moment. Ce mécanisme de communication en temps réel est idéal pour les scénarios où le serveur doit pousser des informations vers le client, comme dans les salons de discussion ou les mises à jour de cours boursiers.

La connexion persistante (longue connexion) est une autre technique qui maintient la connexion entre le client et le serveur ouverte. Contrairement au modèle HTTP à connexion courte, la connexion persistante permet d’envoyer et de recevoir plusieurs paquets de données sur une seule connexion, réduisant ainsi les frais généraux liés à l’établissement et à la fermeture des connexions. Cette technique est utile pour les connexions de bases de données ou les sessions persistantes.

Formats d’échange de données et sérialisation

Comparaison de JSON et XML

JSON (JavaScript Object Notation) et XML (Extensible Markup Language) sont deux formats d’échange de données. JSON est un format léger, facile à lire et à écrire pour les humains, et simple à analyser pour les machines. Sa syntaxe, similaire à celle des objets JavaScript, le rend très populaire dans le développement web.

XML est un langage de balisage utilisé pour stocker et transporter des données. Il offre une meilleure lisibilité et extensibilité. Les données XML sont souvent structurées avec des balises et des attributs, ce qui les rend plus auto-descriptives. Cependant, XML est généralement plus verbeux que JSON.

Le choix entre JSON et XML dépend du projet. Pour les applications web, JSON est souvent préféré car il est plus léger et plus facile à analyser. Pour les échanges de données hautement structurés avec des relations complexes, XML peut être plus approprié.

Implémentation de la sérialisation et de la désérialisation

La sérialisation convertit l’état d’un objet en un format pouvant être stocké ou transmis. Dans les applications web, les objets doivent souvent être sérialisés lorsqu’ils sont envoyés du serveur au client, ou vice versa.

La désérialisation est le processus inverse, qui reconvertit le format sérialisé en objet. Cela permet de restaurer l’objet dans son état d’origine.

Pour JSON, on peut utiliser JSON.stringify() pour sérialiser un objet JavaScript en chaîne JSON, et JSON.parse() pour le désérialiser. Côté serveur, la plupart des langages (Python, Java, PHP) proposent des bibliothèques pour la sérialisation et la désérialisation JSON.

Sécurité réseau et stratégies de protection

Menaces courantes pour la sécurité réseau

Les menaces à la sécurité réseau incluent les actions ou événements pouvant compromettre la sécurité des réseaux, des serveurs ou des données. Les menaces courantes sont :

  • Logiciels malveillants (Malware) : virus, vers, chevaux de Troie, pouvant infecter les systèmes, détruire des fichiers, voler des données ou paralyser les systèmes.
  • Attaques par déni de service (DoS) : submerger la cible avec des requêtes pour la rendre indisponible.
  • Cross-site scripting (XSS) : injection de scripts malveillants dans les pages web, exécutés dans le navigateur des autres utilisateurs.
  • Attaques de l’homme du milieu (MITM) : interception et modification des communications entre deux parties.

Pour se défendre contre ces menaces, il est nécessaire de mettre en œuvre des stratégies de sécurité à plusieurs niveaux.

Chiffrement des données et mesures de protection

Le chiffrement des données est un moyen important de garantir la sécurité des transmissions. Il convertit les données en clair en texte chiffré à l’aide d’algorithmes, de sorte que même si les données sont interceptées, elles ne peuvent pas être lues sans la clé de déchiffrement.

  • SSL/TLS : chiffre le trafic HTTP pour sécuriser les communications entre le site web et l’utilisateur. TLS (Transport Layer Security) est le successeur de SSL.
  • VPN (Virtual Private Network) : crée un tunnel chiffré sur un réseau public pour un accès distant sécurisé.
  • Protection des endpoints : déploiement de logiciels antivirus et de pare-feux sur les appareils clients.
  • Systèmes de détection d’intrusion (IDS) et de prévention d’intrusion (IPS) : surveillent les activités malveillantes sur le réseau et peuvent prendre des mesures pour bloquer les attaques.

Lors de la mise en œuvre de ces mesures, il faut les configurer en fonction des besoins réels et de l’évaluation des risques, afin de se défendre efficacement contre les menaces potentielles sans alourdir inutilement les processus métier.

Stratégies de conception de sécurité et techniques d’optimisation des performances

Dans le cycle de vie du développement logiciel, la conception de la sécurité et l’optimisation des performances sont deux facteurs clés pour garantir la stabilité de l’application et la sécurité des données des utilisateurs. Cette section explore les meilleures pratiques en matière de conception de sécurité, les stratégies d’optimisation des performances, ainsi que les processus de contrôle de version et de collaboration en équipe.

Conception et mise en œuvre de la sécurité du système

La sécurité repose sur la prévention des attaquants qui exploitent les faiblesses logicielles pour accéder au système sans autorisation ou le compromettre. Un système sécurisé doit intégrer des mesures de sécurité à toutes les étapes, de l’écriture du code au déploiement.

Bonnes pratiques de codage sécurisé

Le codage sécurisé implique des techniques et des stratégies visant à prévenir les vulnérabilités logicielles.

  • Validation des entrées : ne jamais faire confiance aux entrées utilisateur ; les valider et les nettoyer rigoureusement. Par exemple, filtrer les caractères spéciaux utilisés dans les injections SQL.
  • Principe du moindre privilège : utiliser les autorisations minimales pour les opérations système et de base de données, en évitant les comptes à privilèges élevés, surtout lors du traitement des données utilisateur.
  • Chiffrement des données sensibles : utiliser des normes de chiffrement solides pour les données stockées ou en transit, comme HTTPS pour les communications et le hachage avec sel pour les mots de passe.

Protection contre la falsification de requête intersite (CSRF)

Les attaques CSRF peuvent amener les utilisateurs à exécuter des actions à leur insu. Pour s’en protéger :

  • Utiliser des jetons CSRF : ajouter un jeton aléatoire dans les formulaires, que le serveur vérifie à chaque soumission.
  • Vérifier l’origine des requêtes : examiner l’en-tête Referer pour s’assurer que la requête provient d’une page de confiance.

Protection contre le cross-site scripting (XSS)

Les attaques XSS permettent aux attaquants d’exécuter des scripts malveillants dans le navigateur des utilisateurs. Pour s’en protéger :

  • Encodage des sorties : échapper les caractères spéciaux lorsque le contenu soumis par l’utilisateur est affiché.
  • Politique de sécurité du contenu (CSP) : utiliser l’en-tête HTTP Content-Security-Policy pour restreindre les ressources pouvant être chargées.

Stratégies d’optimisation des performances du système

L’optimisation des performances vise à améliorer la réactivité et la capacité de traitement de l’application tout en réduisant la consommation de ressources.

Outils de test et de surveillance des performances

Les tests de performance aident à comprendre le comportement de l’application sous charge et à identifier les goulots d’étranglement. Quelques outils courants :

  • Apache JMeter : pour les tests de charge.
  • New Relic : pour la surveillance des performances en temps réel.

Optimisation du code et stratégies de cache

L’optimisation du code et les mécanismes de cache améliorent considérablement les performances.

  • Analyse de code : identifier et optimiser les parties lentes du code.
  • Mise en cache : utiliser un cache en mémoire (comme Redis) pour stocker les données fréquemment utilisées et réduire les accès à la base de données.

Équilibrage de charge et conception d’architecture distribuée

L’équilibrage de charge et la conception de systèmes distribués améliorent l’évolutivité et la haute disponibilité.

  • Équilibreur de charge : utiliser Nginx comme serveur proxy inverse pour répartir les requêtes entre plusieurs serveurs d’application.
  • Architecture microservices : diviser l’application en plusieurs microservices, déployés et mis à l’échelle indépendamment.

Contrôle de version et processus de collaboration en équipe

Dans les projets multi-développeurs, un contrôle de version et un processus de collaboration appropriés sont essentiels pour garantir la qualité du code et l’efficacité du travail.

Stratégies de branche et gestion des conflits de fusion

Adopter une stratégie de branche appropriée améliore l’efficacité du développement et réduit les conflits de fusion.

  • Modèle de branche par fonctionnalité : chaque nouvelle fonctionnalité ou correction est développée sur une branche distincte, fusionnée ensuite dans la branche principale.
  • Résolution des conflits : utiliser des revues de code et des tests pour résoudre les conflits lors des fusions.

Revue de code et processus d’assurance qualité

La revue de code est une étape importante pour améliorer la qualité du code.

  • Normes de revue : établir des normes et des listes de contrôle pour les revues de code.
  • Tests automatisés : utiliser des frameworks de tests unitaires et d’intégration pour valider le code.

Intégration continue et déploiement automatisé

L’intégration continue (CI) et le déploiement automatisé garantissent que le code est toujours prêt à être déployé.

  • Outils CI : Jenkins, Travis CI pour construire et tester automatiquement les changements de code.
  • Déploiement automatisé : utiliser Docker et Kubernetes pour la conteneurisation et l’orchestration, permettant un déploiement automatisé.

Grâce à cette section, les développeurs peuvent améliorer la sécurité et les performances de leurs produits, tout en optimisant les processus de développement en équipe, offrant ainsi une expérience plus sécurisée et plus efficace aux utilisateurs finaux.

Étiquettes: ACCP6.0 enchères en ligne Front-end back-end Sécurité

Publié le 22 juillet à 09h27